/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.ui.test.binding.client;
import static org.jboss.errai.common.client.util.EventTestingUtil.invokeEventListeners;
import static org.jboss.errai.common.client.util.EventTestingUtil.setupAddEventListenerInterceptor;
import java.util.Date;
import org.jboss.errai.databinding.client.api.Converter;
import org.jboss.errai.enterprise.client.cdi.AbstractErraiCDITest;
import org.jboss.errai.ioc.client.container.IOC;
import org.jboss.errai.ui.shared.TemplateUtil;
import org.jboss.errai.ui.test.binding.client.res.BindableEmailAnchor;
import org.jboss.errai.ui.test.binding.client.res.BindingDateConverter;
import org.jboss.errai.ui.test.binding.client.res.BindingTemplate;
import org.jboss.errai.ui.test.binding.client.res.InputElementsModel;
import org.jboss.errai.ui.test.binding.client.res.JsOverlayNumberInputElement;
import org.jboss.errai.ui.test.binding.client.res.JsPropertyDivElement;
import org.jboss.errai.ui.test.binding.client.res.TemplateFragmentWithoutFragmentId;
import org.jboss.errai.ui.test.binding.client.res.TemplateWithDefaultJsOverlayHasValueOverride;
import org.jboss.errai.ui.test.binding.client.res.TemplateWithInputElements;
import org.jboss.errai.ui.test.binding.client.res.TemplateWithJsOverlayHasValue;
import org.jboss.errai.ui.test.binding.client.res.TemplateWithJsPropertyHasValue;
import org.jboss.errai.ui.test.common.client.TestModel;
import org.jboss.errai.ui.test.common.client.dom.Element;
import org.jboss.errai.ui.test.common.client.dom.TextInputElement;
import org.junit.Test;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
/**
* Tests for the Errai UI/DataBinding integration.
*
* @author Christian Sadilek <csadilek@redhat.com>
*/
public class BindingTemplateTest extends AbstractErraiCDITest {
private static interface PropertyHandler<V> {
void setProperty(V value);
V getProperty();
}
private static class DefaultInputElementHandler implements PropertyHandler<String> {
private final InputElement element;
private DefaultInputElementHandler(final InputElement element) {
this.element = element;
}
@Override
public void setProperty(final String value) {
element.setValue(value);
}
@Override
public String getProperty() {
return element.getValue();
}
}
private static class CheckboxHandler implements PropertyHandler<Boolean> {
private final InputElement element;
private CheckboxHandler(final InputElement element) {
this.element = element;
}
@Override
public void setProperty(final Boolean value) {
element.setChecked(value);
}
@Override
public Boolean getProperty() {
return element.isChecked();
}
}
private static class IdentityConverter<T> implements Converter<T, T> {
private final Class<T> type;
private IdentityConverter(final Class<T> type) {
this.type = type;
}
@Override
public T toModelValue(final T widgetValue) {
return widgetValue;
}
@Override
public T toWidgetValue(final T modelValue) {
return modelValue;
}
@Override
public Class<T> getModelType() {
return type;
}
@Override
public Class<T> getComponentType() {
return type;
}
}
private static class DoubleConverter implements Converter<Double, String> {
@Override
public Double toModelValue(final String widgetValue) {
return Double.valueOf(widgetValue);
}
@Override
public String toWidgetValue(final Double modelValue) {
return String.valueOf(modelValue);
}
@Override
public Class<Double> getModelType() {
return Double.class;
}
@Override
public Class<String> getComponentType() {
return String.class;
}
}
private static class IntegerConverter implements Converter<Integer, String> {
@Override
public Integer toModelValue(final String widgetValue) {
return Integer.valueOf(widgetValue);
}
@Override
public String toWidgetValue(final Integer modelValue) {
return String.valueOf(modelValue);
}
@Override
public Class<Integer> getModelType() {
return Integer.class;
}
@Override
public Class<String> getComponentType() {
return String.class;
}
}
@Override
public String getModuleName() {
return getClass().getName().replaceAll("client.*$", "Test");
}
@Override
protected void gwtSetUp() throws Exception {
setupAddEventListenerInterceptor();
super.gwtSetUp();
}
@Test
public void testAutomaticBindingWithCompositeTemplated() {
final BindingTemplateTestApp app = IOC.getBeanManager().lookupBean(BindingTemplateTestApp.class).getInstance();
automaticBindingAssertions(app.getCompositeTemplate());
}
@Test
public void testAutomaticBindingWithNonCompositeTemplated() {
final BindingTemplateTestApp app = IOC.getBeanManager().lookupBean(BindingTemplateTestApp.class).getInstance();
automaticBindingAssertions(app.getNonCompositeTemplate());
}
@Test
public void testInputElementBindings() throws Exception {
final TemplateWithInputElements bean = IOC.getBeanManager().lookupBean(TemplateWithInputElements.class).getInstance();
final InputElementsModel model = bean.binder.getModel();
inputElementAssertions(new PropertyHandler<String>() {
@Override
public void setProperty(final String value) {
model.setText(value);
}
@Override
public String getProperty() {
return model.getText();
}
}, new DefaultInputElementHandler(bean.text), new IdentityConverter<>(String.class), "text", "test value", "other test value", bean.text);
inputElementAssertions(new PropertyHandler<String>() {
@Override
public void setProperty(final String value) {
model.setPassword(value);
}
@Override
public String getProperty() {
return model.getPassword();
}
}, new DefaultInputElementHandler(bean.password), new IdentityConverter<>(String.class), "password", "password123", "letmein123", bean.password);
inputElementAssertions(new PropertyHandler<Double>() {
@Override
public void setProperty(final Double value) {
model.setNumber(value);
}
@Override
public Double getProperty() {
return model.getNumber();
}
}, new DefaultInputElementHandler(bean.number), new DoubleConverter(), "number", 1.0, "2.0", bean.number);
inputElementAssertions(new PropertyHandler<Integer>() {
@Override
public void setProperty(final Integer value) {
model.setRange(value);
}
@Override
public Integer getProperty() {
return model.getRange();
}
}, new DefaultInputElementHandler(bean.range), new IntegerConverter(), "range", 10, "20", bean.range);
inputElementAssertions(new PropertyHandler<Boolean>() {
@Override
public void setProperty(final Boolean value) {
model.setCheckbox(value);
}
@Override
public Boolean getProperty() {
return model.isCheckbox();
}
}, new CheckboxHandler(bean.checkbox), new IdentityConverter<>(Boolean.class), "checkbox", true, false, bean.checkbox);
inputElementAssertions(new PropertyHandler<String>() {
@Override
public void setProperty(final String value) {
model.setFile(value);
}
@Override
public String getProperty() {
return model.getFile();
}
}, new DefaultInputElementHandler(bean.file), new IdentityConverter<>(String.class), "file", "file:///tmp/foo", "file:///tmp/bar", bean.file);
inputElementAssertions(new PropertyHandler<String>() {
@Override
public void setProperty(final String value) {
model.setEmail(value);
}
@Override
public String getProperty() {
return model.getEmail();
}
}, new DefaultInputElementHandler(bean.email), new IdentityConverter<>(String.class), "email", "a@b.c", "y@z", bean.email);
inputElementAssertions(new PropertyHandler<String>() {
@Override
public void setProperty(final String value) {
model.setColor(value);
}
@Override
public String getProperty() {
return model.getColor();
}
}, new DefaultInputElementHandler(bean.color), new IdentityConverter<>(String.class), "color", "#000000", "#FFFFFF", bean.color);
inputElementAssertions(new PropertyHandler<Boolean>() {
@Override
public void setProperty(final Boolean value) {
model.setRadio(value);
}
@Override
public Boolean getProperty() {
return model.getRadio();
}
}, new CheckboxHandler(bean.radio), new IdentityConverter<>(Boolean.class), "radio", true, false, bean.radio);
inputElementAssertions(new PropertyHandler<String>() {
@Override
public void setProperty(final String value) {
model.setTel(value);
}
@Override
public String getProperty() {
return model.getTel();
}
}, new DefaultInputElementHandler(bean.tel), new IdentityConverter<>(String.class), "tel", "4161234567", "6473217654", bean.tel);
inputElementAssertions(new PropertyHandler<String>() {
@Override
public void setProperty(final String value) {
model.setUrl(value);
}
@Override
public String getProperty() {
return model.getUrl();
}
}, new DefaultInputElementHandler(bean.url), new IdentityConverter<>(String.class), "url", "http://jboss.org", "https://redhat.com", bean.url);
}
@Test
public void testBindingToJsTypeWithJsOverlayHasValue() throws Exception {
final TemplateWithJsOverlayHasValue bean = IOC.getBeanManager().lookupBean(TemplateWithJsOverlayHasValue.class).getInstance();
final InputElementsModel model = bean.binder.getModel();
final JsOverlayNumberInputElement presenter = bean.number;
assertNull("Model value should be null.", model.getNumber());
assertNull("UI value should be null.", presenter.getValue());
try {
model.setNumber(1.0);
} catch (final Throwable t) {
throw new RuntimeException("An error occurred while setting model property: " + t.getMessage(), t);
}
assertEquals("UI value was not updated.", model.getNumber(), presenter.getValue());
try {
presenter.setValue(2.0);
invokeEventListeners(TemplateUtil.asElement(presenter), "change");
} catch (final Throwable t) {
throw new RuntimeException("An error occurred while setting the UI value: " + t.getMessage(), t);
}
assertEquals("Model value was not updated.", presenter.getValue(), model.getNumber());
}
@Test
public void testBindingToJsTypeWithJsPropertyHasValue() throws Exception {
final TemplateWithJsPropertyHasValue bean = IOC.getBeanManager().lookupBean(TemplateWithJsPropertyHasValue.class).getInstance();
final InputElementsModel model = bean.binder.getModel();
final JsPropertyDivElement elementWrapper = bean.text;
assertNull("Model value should be null.", model.getText());
assertNull("UI value should be null.", elementWrapper.getValue());
try {
model.setText("1.0");
} catch (final Throwable t) {
throw new RuntimeException("An error occurred while setting model property: " + t.getMessage(), t);
}
assertEquals("UI value was not updated.", model.getText(), elementWrapper.getValue());
try {
elementWrapper.setValue("2.0");
invokeEventListeners(TemplateUtil.asElement(elementWrapper), "change");
} catch (final Throwable t) {
throw new RuntimeException("An error occurred while setting the UI value: " + t.getMessage(), t);
}
assertEquals("Model value was not updated.", elementWrapper.getValue(), model.getText());
}
@Test
public void testBindingToJsTypeInterfaceWithJsOverlayHasValue() throws Exception {
final TemplateWithDefaultJsOverlayHasValueOverride bean = IOC.getBeanManager().lookupBean(TemplateWithDefaultJsOverlayHasValueOverride.class).getInstance();
final InputElementsModel model = bean.binder.getModel();
final BindableEmailAnchor elementWrapper = bean.email;
assertNull("Model value should be null.", model.getEmail());
assertEquals("UI value should be empty.", "", elementWrapper.getValue());
try {
model.setEmail("a@b");
} catch (final Throwable t) {
throw new RuntimeException("An error occurred while setting model property: " + t.getMessage(), t);
}
assertEquals("UI value was not updated.", model.getEmail(), elementWrapper.getValue());
try {
elementWrapper.setValue("b@a");
invokeEventListeners(TemplateUtil.asElement(elementWrapper), "change");
} catch (final Throwable t) {
throw new RuntimeException("An error occurred while setting the UI value: " + t.getMessage(), t);
}
assertEquals("Model value was not updated.", elementWrapper.getValue(), model.getEmail());
}
/**
* Regression test for ERRAI-779
*/
@Test
public void testTemplateFragmentContainingWordBodyIsParsedWithoutError() throws Exception {
try {
IOC.getBeanManager()
.lookupBean(TemplateFragmentWithoutFragmentId.class).getInstance();
}
catch (final Exception e) {
fail("Loading templated instance caused an error: " + e.getMessage());
}
}
private <M, U> void inputElementAssertions(final PropertyHandler<M> model, final PropertyHandler<U> ui,
final Converter<M, U> converter, final String type, final M value1, final U value2, final InputElement element) {
assertNotNull("The element for input[type='" + type + "'] was not injected.", element);
assertEquals("The element for input[type='" + type + "'] has the wrong tag name.", "INPUT", element.getTagName());
assertEquals("The element for input[type='" + type + "'] has the wrong type.", type, element.getType());
assertFalse("Precondition failed: ui property already set to [" + value1 + "].", converter.toWidgetValue(value1).equals(ui.getProperty()));
try {
model.setProperty(value1);
} catch (final Throwable t) {
throw new RuntimeException("An error occurred setting [" + value1.toString() + "] for the model property.", t);
}
assertEquals("The UI value for input[type='" + type + "'] was not updated after a model change.",
converter.toWidgetValue(value1), ui.getProperty());
try {
ui.setProperty(value2);
invokeEventListeners(TemplateUtil.asElement(element), "change");
} catch (final Throwable t) {
throw new RuntimeException(
"An error occurred for binding of input[type=" + type + "] setting ["
+ value2.toString() + "] for the ui value.", t);
}
assertEquals("The model value for input[type='" + type + "'] was not updated after a UI change.",
converter.toModelValue(value2), model.getProperty());
}
private void automaticBindingAssertions(final BindingTemplate<?> template) {
assertNotNull("Template instance was not injected!", template);
final Label idLabel = template.getIdLabel();
assertNotNull(idLabel);
assertEquals("", idLabel.getText());
final DivElement idDiv = template.getIdDiv();
assertNotNull(idDiv);
assertEquals("", idDiv.getInnerText());
final TextBox nameTextBox = template.getNameTextBox();
assertNotNull(nameTextBox);
assertEquals("", nameTextBox.getValue());
final TextBox dateTextBox = template.getDateTextBox();
assertNotNull(dateTextBox);
assertEquals("", dateTextBox.getValue());
final TextBox phoneNumberBox = template.getPhoneNumberBox();
assertNotNull(phoneNumberBox);
assertEquals("", phoneNumberBox.getValue());
final Element title = template.getTitleField();
assertNotNull(title);
assertEquals("", title.getInnerHTML());
final TextInputElement age = template.getAge();
assertNotNull(age);
assertEquals("", age.getValue());
final TestModel model = template.getModel();
model.setId(1711);
model.getChild().setName("errai");
model.setLastChanged(new Date());
model.setPhoneNumber("+1 555");
model.setAge(50);
model.setTitle("Mr.");
assertEquals("Div (id) was not updated!", Integer.valueOf(model.getId()).toString(), idDiv.getInnerText());
assertEquals("Label (id) was not updated!", Integer.valueOf(model.getId()).toString(), idLabel.getText());
assertEquals("TextBox (name) was not updated!", model.getChild().getName(), nameTextBox.getValue());
assertEquals("TextBox (date) was not updated using custom converter!", "testdate", dateTextBox.getValue());
assertEquals("TextBox (phoneNumber) was not updated", model.getPhoneNumber(), phoneNumberBox.getValue());
assertEquals("Element (titleField) was not updated!", model.getTitle(), title.getInnerHTML());
assertEquals("Element (age) was not updated!", Integer.valueOf(model.getAge()).toString(), age.getValue());
nameTextBox.setValue("updated", true);
dateTextBox.setValue("updated", true);
phoneNumberBox.setValue("+43 555", true);
age.setValue("51");
fireChangeEvent(age);
assertEquals("Model (name) was not updated!", nameTextBox.getValue(), model.getChild().getName());
assertEquals("Model (lastUpdate) was not updated using custom converter!",
BindingDateConverter.TEST_DATE, model.getLastChanged());
assertEquals("Model (phoneNumber) was not updated!", phoneNumberBox.getValue(), model.getPhoneNumber());
assertEquals("Model (age) was not updated!", Integer.valueOf(age.getValue()), model.getAge());
}
private void fireChangeEvent(final Object element) {
final NativeEvent changeEvent = Document.get().createChangeEvent();
TemplateUtil.asElement(element).dispatchEvent(changeEvent);
}
}