Author: sylvain Date: Sun Nov 21 14:28:33 2004 New Revision: 106131 Added: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.java (contents, props changed) cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.model.xml (contents, props changed) cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.xtest cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/WidgetTestHelper.java (contents, props changed) Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Field.java Log: Fix a bug when Field.setValue() is called, add some tests
Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Field.java Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Field.java?view=diff&rev=106131&p1=cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Field.java&r1=106130&p2=cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Field.java&r2=106131 ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Field.java (original) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/java/org/apache/cocoon/forms/formmodel/Field.java Sun Nov 21 14:28:33 2004 @@ -155,11 +155,27 @@ "\" (expected " + getDatatype().getTypeClass() + ", got " + newValue.getClass() + ")."); } - Object oldValue = this.value; - boolean changed = ! (oldValue == null ? "" : oldValue).equals(newValue == null ? "" : newValue); + + // Is it a new value? + boolean changed; + if (this.valueState == VALUE_UNPARSED) { + // Current value was not parsed + changed = true; + } else if (this.value == null) { + // Is current value not null? + changed = (newValue != null); + } else { + // Is current value different? + changed = !this.value.equals(newValue); + } + // Do something only if value is different or null // (null allows to reset validation error) if (changed || newValue == null) { + // Do we need to call listeners? If yes, keep (and parse if needed) old value. + boolean callListeners = changed && hasValueChangedListeners(); + Object oldValue = callListeners ? getValue() : null; + this.value = newValue; this.validationError = null; // Force validation, even if set by the application @@ -169,7 +185,8 @@ } else { this.enteredValue = null; } - if (changed && hasValueChangedListeners()) { + + if (callListeners) { getForm().addWidgetEvent(new ValueChangedEvent(this, oldValue, newValue)); } } @@ -200,7 +217,13 @@ // Only convert if the text value actually changed. Otherwise, keep the old value // and/or the old validation error (allows to keep errors when clicking on actions) - if (!(newEnteredValue == null ? "" : newEnteredValue).equals((enteredValue == null ? "" : enteredValue))) { + boolean changed; + if (enteredValue == null) { + changed = (newEnteredValue != null); + } else { + changed = !enteredValue.equals(newEnteredValue); + } + if (changed) { // If we have some value-changed listeners, we must make sure the current value has been // parsed, to fill the event. Otherwise, we don't need to spend that extra CPU time. Added: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.java Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.java?view=auto&rev=106131 ============================================================================== --- (empty file) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.java Sun Nov 21 14:28:33 2004 @@ -0,0 +1,227 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * 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.apache.cocoon.forms.formmodel; + +import org.apache.cocoon.core.container.ContainerTestCase; +import org.apache.cocoon.environment.mock.MockRequest; +import org.apache.cocoon.forms.FormContext; +import org.apache.cocoon.forms.event.ValueChangedEvent; +import org.apache.cocoon.forms.event.ValueChangedListener; +import org.w3c.dom.Document; + +/** + * Test case for CForm's Field widget + * + * @version $Id$ + */ + +public class FieldTestCase extends ContainerTestCase { + + public static final String VALUE_PATH = "fi:fragment/fi:field/fi:value"; + public static final String VALIDATION_PATH = "fi:fragment/fi:field/fi:validation-message"; + + + /** + * Nominal test where the request data is syntactically correct and validates + */ + public void testValueDoesParseAndValidate() throws Exception { + Form form = WidgetTestHelper.loadForm(getManager(), this, "FieldTestCase.model.xml"); + Field field = (Field)form.getChild("intfield"); + Action button = (Action)form.getChild("action"); + MockRequest request; + + request = new MockRequest(); + request.addParameter("intfield", "11"); + request.addParameter("action", "pressed"); + form.process(new FormContext(request)); + + // No parsing nor validation where performed + Document doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathEquals("Displayed value", "11", VALUE_PATH, doc); + WidgetTestHelper.assertXPathNotExists("Validation error", VALIDATION_PATH, doc); + + // Now do some parsing. + assertEquals("Field value", new Integer(11), field.getValue()); + // And still no validation error (do not call getValidationError() as it does validate) + doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathNotExists("Validation error", VALIDATION_PATH, doc); + + // Now validate + assertTrue("Field does validate", field.validate()); + assertNull("getValidationError() null after validation", field.getValidationError()); + doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathNotExists("Validation error", VALIDATION_PATH, doc); + } + + /** + * Request data is not syntactically correct + */ + public void testValueDoesNotParse() throws Exception { + Form form = WidgetTestHelper.loadForm(getManager(), this, "FieldTestCase.model.xml"); + Field field = (Field)form.getChild("intfield"); + Action button = (Action)form.getChild("action"); + MockRequest request; + + request = new MockRequest(); + request.addParameter("intfield", "foo"); + request.addParameter("action", "pressed"); + form.process(new FormContext(request)); + + // No parsing nor validation where performed + Document doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathEquals("Displayed velue", "foo", VALUE_PATH, doc); + WidgetTestHelper.assertXPathNotExists("Validation error before parse", VALIDATION_PATH, doc); + + // Now do some parsing. Will return null as it's not parseable + assertNull("Field value", field.getValue()); + // But still no validation error + doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathEquals("Displayed value", "foo", VALUE_PATH, doc); + WidgetTestHelper.assertXPathNotExists("Validation error after parse", VALIDATION_PATH, doc); + + // Now validate + assertFalse("Field validation", field.validate()); + doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathEquals("Displayed velue", "foo", VALUE_PATH, doc); + WidgetTestHelper.assertXPathExists("Validation not null after parse", VALIDATION_PATH, doc); + assertNotNull("getValidationError() not null after validation", field.getValidationError()); + } + + /** + * Request data is syntactically correct but doesn't validate + */ + public void testValueDoesNotValidate() throws Exception { + Form form = WidgetTestHelper.loadForm(getManager(), this, "FieldTestCase.model.xml"); + Field field = (Field)form.getChild("intfield"); + Action button = (Action)form.getChild("action"); + MockRequest request; + + request = new MockRequest(); + request.addParameter("intfield", "1"); + request.addParameter("action", "pressed"); + form.process(new FormContext(request)); + + // No parsing nor validation where performed + Document doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathEquals("Displayed value", "1", VALUE_PATH, doc); + WidgetTestHelper.assertXPathNotExists("Validation error before parse", VALIDATION_PATH, doc); + + // Now do some parsing. Will return null although syntactically correct as it's invalid + assertNull("Field value", field.getValue()); + // But still no validation error + doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathNotExists("Validation error after parse", VALIDATION_PATH, doc); + + // Now validate + assertFalse("Field validation", field.validate()); + doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathExists("Validation error after validation", VALIDATION_PATH, doc); + assertNotNull("getValidationError() not null after validation", field.getValidationError()); + } + + /** + * Test that a field's value is properly set by a call to setValue("") with an + * empty string when the field is in unparsed state (there used to be a bug in + * that case) + */ + public void testSetEmptyValueWhenValueChangedOnRequest() throws Exception { + Form form = WidgetTestHelper.loadForm(getManager(), this, "FieldTestCase.model.xml"); + Field field = (Field)form.getChild("stringfield"); + Action button = (Action)form.getChild("action"); + MockRequest request; + + // Set a value in stringfield and submit with an action + // (no validation, thus no call to doParse()) + request = new MockRequest(); + request.addParameter("stringfield", "bar"); + request.addParameter("action", "pressed"); + form.process(new FormContext(request)); + + // Verify submit widget, just to be sure that validation did not occur + assertEquals("Form submit widget", button, form.getSubmitWidget()); + + // Set the value to an empty string. In that case, a faulty test made + // it actually ignore it when state was VALUE_UNPARSED + field.setValue(""); + + // Check value by various means + Document doc = WidgetTestHelper.getWidgetFragment(field, null); + WidgetTestHelper.assertXPathEquals("Displayed value", "", VALUE_PATH, doc); + assertEquals("Datatype string conversion", "", field.getDatatype().convertToString(field.value, null)); + assertEquals("Field value", "", (String)field.getValue()); + } + + /** + * Test that the previous field value is correctly passed to event listeners + * even if it was not already parsed. + */ + public void testOldValuePresentInEventEvenIfNotParsed() throws Exception { + Form form = WidgetTestHelper.loadForm(getManager(), this, "FieldTestCase.model.xml"); + Field field = (Field)form.getChild("stringfield"); + Action button = (Action)form.getChild("action"); + MockRequest request; + + // Set a value on "stringfield", and submit using an action so that + // it stays in unparsed state + request = new MockRequest(); + request.addParameter("stringfield", "foo"); + request.addParameter("action", "pressed"); + form.process(new FormContext(request)); + + // Now add an event listener that will check old an new value + field.addValueChangedListener(new ValueChangedListener (){ + public void valueChanged(ValueChangedEvent event) { + assertEquals("Old value", "foo", (String)event.getOldValue()); + assertEquals("New value", "bar", (String)event.getNewValue()); + } + }); + + // Change value to "bar", still without explicit validation + // That will call the event listener + request = new MockRequest(); + request.addParameter("stringfield", "bar"); + request.addParameter("button", "pressed"); + form.process(new FormContext(request)); + } + + /** + * Request parameters are not read when a field is not in active state + */ + public void testParameterNotReadWhenDisabled() throws Exception { + Form form = WidgetTestHelper.loadForm(getManager(), this, "FieldTestCase.model.xml"); + Field field = (Field)form.getChild("stringfield"); + MockRequest request; + + // Disable the form + form.setState(WidgetState.DISABLED); + field.setValue("foo"); + + request = new MockRequest(); + request.addParameter("stringfield", "bar"); + form.process(new FormContext(request)); + + // Check that "bar" was not read + assertEquals("foo", field.getValue()); + + // Switch back to active and resumbit the same request + form.setState(WidgetState.ACTIVE); + form.process(new FormContext(request)); + + // Should have changed now + assertEquals("bar", field.getValue()); + } +} Added: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.model.xml Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.model.xml?view=auto&rev=106131 ============================================================================== --- (empty file) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.model.xml Sun Nov 21 14:28:33 2004 @@ -0,0 +1,19 @@ +<fd:form xmlns:fd="http://apache.org/cocoon/forms/1.0#definition"> + <fd:widgets> + <fd:field id="stringfield"> + <fd:datatype base="string"/> + </fd:field> + + <fd:field id="intfield"> + <fd:datatype base="integer"> + <fd:convertor type="plain"/> + </fd:datatype> + <fd:validation> + <fd:range min="10"/> + </fd:validation> + </fd:field> + + <fd:action id="action" action-command="blah"/> + + </fd:widgets> +</fd:form> \ No newline at end of file Added: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.xtest Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.xtest?view=auto&rev=106131 ============================================================================== --- (empty file) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/FieldTestCase.xtest Sun Nov 21 14:28:33 2004 @@ -0,0 +1,81 @@ +<?xml version="1.0" ?> +<!-- + Copyright 1999-2004 The Apache Software Foundation + + 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. +--> +<testcase> + <roles> + + <role name="org.apache.cocoon.forms.datatype.DatatypeManager" + shorthand="forms-datatype" + default-class="org.apache.cocoon.forms.datatype.DefaultDatatypeManager"/> + + <role name="org.apache.cocoon.forms.expression.ExpressionManager" + shorthand="forms-expression" + default-class="org.apache.cocoon.forms.expression.DefaultExpressionManager"/> + + <role name="org.apache.cocoon.forms.FormManager" + shorthand="forms-formmanager" + default-class="org.apache.cocoon.forms.DefaultFormManager"/> + + <role name="org.apache.cocoon.forms.CacheManager" + shorthand="forms-cachemanager" + default-class="org.apache.cocoon.forms.DefaultCacheManager"/> + + <role name="org.apache.cocoon.forms.validation.WidgetValidatorBuilderSelector" + shorthand="forms-validators" + default-class="org.apache.cocoon.components.ExtendedComponentSelector"/> + + <role name="org.apache.cocoon.forms.event.WidgetListenerBuilderSelector" + shorthand="forms-widgetlisteners" + default-class="org.apache.cocoon.components.ExtendedComponentSelector"/> +</roles> + + <components> + <forms-datatype logger="forms"> + <datatypes> + <datatype name="string" src="org.apache.cocoon.forms.datatype.typeimpl.StringTypeBuilder"> + <convertors default="dummy" plain="dummy"> + <convertor name="dummy" src="org.apache.cocoon.forms.datatype.convertor.DummyStringConvertorBuilder"/> + </convertors> + </datatype> + <datatype name="integer" src="org.apache.cocoon.forms.datatype.typeimpl.IntegerTypeBuilder"> + <convertors default="formatting" plain="plain"> + <convertor name="plain" src="org.apache.cocoon.forms.datatype.convertor.PlainIntegerConvertorBuilder"/> + <convertor name="formatting" src="org.apache.cocoon.forms.datatype.convertor.FormattingIntegerConvertorBuilder"/> + </convertors> + </datatype> + </datatypes> + <!--validation-rules> + <validation-rule name="range" src="org.apache.cocoon.forms.datatype.validationruleimpl.RangeValidationRuleBuilder"/> + </validation-rules--> + </forms-datatype> + + <forms-validators> + <validator name="range" class="org.apache.cocoon.forms.validation.impl.RangeValidatorBuilder"/> + </forms-validators> + + <forms-formmanager> + <widgets> + <widget name="form" src="org.apache.cocoon.forms.formmodel.FormDefinitionBuilder"/> + <widget name="field" src="org.apache.cocoon.forms.formmodel.FieldDefinitionBuilder"/> + <widget name="action" src="org.apache.cocoon.forms.formmodel.ActionDefinitionBuilder"/> + </widgets> + </forms-formmanager> + + <forms-expression logger="forms.expression"/> + + </components> + +</testcase> Added: cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/WidgetTestHelper.java Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/WidgetTestHelper.java?view=auto&rev=106131 ============================================================================== --- (empty file) +++ cocoon/branches/BRANCH_2_1_X/src/blocks/forms/test/org/apache/cocoon/forms/formmodel/WidgetTestHelper.java Sun Nov 21 14:28:33 2004 @@ -0,0 +1,140 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * 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.apache.cocoon.forms.formmodel; + +import java.util.Locale; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import junit.framework.Assert; + +import org.apache.avalon.framework.service.ServiceManager; +import org.apache.cocoon.forms.Constants; +import org.apache.cocoon.forms.FormManager; +import org.apache.cocoon.xml.AttributesImpl; +import org.apache.cocoon.xml.dom.DOMBuilder; +import org.apache.commons.jxpath.JXPathContext; +import org.apache.commons.jxpath.Pointer; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.NamespaceSupport; + +/** + * Helper class to build Widget test cases. + * + * @version $Id$ + */ +public class WidgetTestHelper { + + // Private constructor as we only have static methods + private WidgetTestHelper() {} + + /** + * Get the result of a widget's generateSaxFragment() method as a Document. + * <p> + * The widget's fragment is encapsulated in a root <fi:fragment> element, + * since there's no guarantee that a widget outputs a single top-level element + * (there can be several elements, or even none if the widget is invisible) + * + * @param widget the widget of which we want the fragment + * @param locale the locale to be used to generate the fragment + * @return the document containing the fragment + */ + public static Document getWidgetFragment(Widget widget, Locale locale) throws SAXException { + + DOMBuilder domBuilder = new DOMBuilder(); + // Start document and "fi:fragment" root element + domBuilder.startDocument(); + domBuilder.startPrefixMapping(Constants.INSTANCE_PREFIX, Constants.INSTANCE_NS); + // FIXME: why simply declaring the prefix isn't enough? + AttributesImpl attr = new AttributesImpl(); + attr.addCDATAAttribute(NamespaceSupport.XMLNS, "fi:", "xmlns:fi", Constants.INSTANCE_NS); + domBuilder.startElement(Constants.INSTANCE_NS, "fragment", Constants.INSTANCE_PREFIX_COLON + "fragment", attr); + + widget.generateSaxFragment(domBuilder, locale); + + // End "fi:fragment" element and document + domBuilder.endElement(Constants.INSTANCE_NS, "fragment", Constants.INSTANCE_PREFIX_COLON + "fragment"); + domBuilder.endPrefixMapping(Constants.INSTANCE_PREFIX); + domBuilder.endDocument(); + + // Return the document + return domBuilder.getDocument(); + } + + public static void assertXPathEquals(String expected, String xpath, Document doc) { + // use xpath as the message + assertXPathEquals(xpath, expected, xpath, doc); + } + + public static void assertXPathEquals(String message, String expected, String xpath, Document doc) { + JXPathContext ctx = JXPathContext.newContext(doc); + ctx.setLenient(true); + Assert.assertEquals(message, expected, ctx.getValue(xpath)); + } + + public static void assertXPathExists(String xpath, Document doc) { + // use xpath as message + assertXPathExists(xpath, xpath, doc); + } + + public static void assertXPathExists(String message, String xpath, Document doc) { + JXPathContext ctx = JXPathContext.newContext(doc); + ctx.setLenient(true); + Pointer pointer = ctx.getPointer(xpath); + Assert.assertNotNull(message, pointer.getNode()); + } + + public static void assertXPathNotExists(String xpath, Document doc) { + // use xpath as message + assertXPathNotExists(xpath, xpath, doc); + } + + public static void assertXPathNotExists(String message, String xpath, Document doc) { + JXPathContext ctx = JXPathContext.newContext(doc); + ctx.setLenient(true); + Pointer pointer = ctx.getPointer(xpath); + Assert.assertNull(message, pointer.getNode()); + } + + /** + * Load a Form whose definition relative to a given object (typically, the TestCase class). + * + * @param manager the ServiceManager that will be used to create the form + * @param obj the object relative to which the resource will be read + * @param resource the relative resource name for the form definition + * @return the Form + * @throws Exception + */ + public static Form loadForm(ServiceManager manager, Object obj, String resource) throws Exception { + // Load the document + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // Grmbl... why isn't this true by default? + factory.setNamespaceAware(true); + DocumentBuilder parser = factory.newDocumentBuilder(); + Document doc = parser.parse(obj.getClass().getResource(resource).toExternalForm()); + + // Create the form + FormManager formManager = (FormManager)manager.lookup(FormManager.ROLE); + try { + return formManager.createForm(doc.getDocumentElement()); + } finally { + manager.release(formManager); + } + } +}