/* * Copyright 2010 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.editor.client; import com.google.gwt.core.client.GWT; import com.google.gwt.editor.client.adapters.EditorSource; import com.google.gwt.editor.client.adapters.ListEditor; import com.google.gwt.editor.client.adapters.OptionalFieldEditor; import com.google.gwt.editor.client.adapters.SimpleEditor; import com.google.gwt.junit.client.GWTTestCase; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; /** * Uses the SimpleBeanEditorTest to test core Editor behaviors as generated by * AbstractEditorDriverGenerator. */ public class SimpleBeanEditorTest extends GWTTestCase { class AddressCoEditorView extends LeafAddressEditor implements IsEditor<LeafAddressEditor> { private LeafAddressEditor addressEditor = new LeafAddressEditor(); public LeafAddressEditor asEditor() { return addressEditor; } } class AddressEditorPartOne implements Editor<Address> { SimpleEditor<String> city = SimpleEditor.of(UNINITIALIZED); } class AddressEditorPartTwo implements Editor<Address> { SimpleEditor<String> street = SimpleEditor.of(UNINITIALIZED); } class AddressEditorView implements IsEditor<LeafAddressEditor> { LeafAddressEditor addressEditor = new LeafAddressEditor(); public LeafAddressEditor asEditor() { return addressEditor; } } class LeafAddressEditor extends AddressEditor implements LeafValueEditor<Address> { /* * These two fields are used to ensure that getValue() and setValue() aren't * called excessively. */ int getValueCalled; int setValueCalled; Address value; public Address getValue() { getValueCalled++; return value; } public void setValue(Address value) { setValueCalled++; value = new Address(); } } interface ListEditorDriver extends SimpleBeanEditorDriver<List<String>, ListEditor<String, SimpleEditor<String>>> { } class PersonEditorWithAddressEditorView implements Editor<Person> { AddressEditorView addressEditor = new AddressEditorView(); SimpleEditor<String> name = SimpleEditor.of(UNINITIALIZED); } interface PersonEditorWithAddressEditorViewDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithAddressEditorView> { } /** * A test for assigning the object associated with an editor to an immediate * child editors. */ class PersonEditorWithAliasedSubEditors implements Editor<Person> { @Path("") PersonEditor e1 = new PersonEditor(); @Path("") PersonEditor e2 = new PersonEditor(); } interface PersonEditorWithAliasedSubEditorsDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithAliasedSubEditors> { } class PersonEditorWithCoAddressEditorView implements Editor<Person> { AddressCoEditorView addressEditor = new AddressCoEditorView(); SimpleEditor<String> name = SimpleEditor.of(UNINITIALIZED); } interface PersonEditorWithCoAddressEditorViewDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithCoAddressEditorView> { } static class PersonEditorWithDelegate extends PersonEditor implements HasEditorDelegate<Person> { EditorDelegate<Person> delegate; public void setDelegate(EditorDelegate<Person> delegate) { this.delegate = delegate; } } interface PersonEditorWithDelegateDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithDelegate> { } class PersonEditorWithLeafAddressEditor implements Editor<Person> { LeafAddressEditor addressEditor = new LeafAddressEditor(); SimpleEditor<String> name = SimpleEditor.of(UNINITIALIZED); @Path("manager.name") SimpleEditor<String> managerName = SimpleEditor.of(UNINITIALIZED); } interface PersonEditorWithLeafAddressEditorDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithLeafAddressEditor> { } class PersonEditorWithManagerNameWithDelegate implements Editor<Person> { @Path("manager.name") SimpleEditorWithDelegate<String> managerName = new SimpleEditorWithDelegate<String>( UNINITIALIZED); } interface PersonEditorWithManagerNameWithDelegateDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithManagerNameWithDelegate> { } class PersonEditorWithMultipleBindings implements Editor<Person> { @Editor.Path("address") AddressEditorPartOne one = new AddressEditorPartOne(); @Editor.Path("address") AddressEditorPartTwo two = new AddressEditorPartTwo(); } interface PersonEditorWithMultipleBindingsDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithMultipleBindings> { } interface PersonEditorWithOptionalAddressDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithOptionalAddressEditor> { } static class PersonEditorWithOptionalAddressEditor implements Editor<Person> { OptionalFieldEditor<Address, AddressEditor> address; SimpleEditor<String> name = SimpleEditor.of(UNINITIALIZED); public PersonEditorWithOptionalAddressEditor(AddressEditor delegate) { address = OptionalFieldEditor.of(delegate); } } class PersonEditorWithValueAwareAddressEditor implements Editor<Person> { ValueAwareAddressEditor addressEditor = new ValueAwareAddressEditor(); SimpleEditor<String> name = SimpleEditor.of(UNINITIALIZED); @Path("manager.name") SimpleEditor<String> managerName = SimpleEditor.of(UNINITIALIZED); } interface PersonEditorWithValueAwareAddressEditorDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithValueAwareAddressEditor> { } class PersonEditorWithValueAwareLeafAddressEditor implements Editor<Person> { ValueAwareLeafAddressEditor addressEditor = new ValueAwareLeafAddressEditor(); SimpleEditor<String> name = SimpleEditor.of(UNINITIALIZED); @Path("manager.name") SimpleEditor<String> managerName = SimpleEditor.of(UNINITIALIZED); } interface PersonEditorWithValueAwareLeafAddressEditorDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithValueAwareLeafAddressEditor> { } class PersonWithList { String name = "PersonWithList"; List<Address> addresses = new ArrayList<Address>(); public List<Address> getAddresses() { return addresses; } public String getName() { return name; } } class PersonWithListEditor implements Editor<PersonWithList> { SimpleEditor<String> nameEditor = SimpleEditor.of(UNINITIALIZED); ListEditor<Address, AddressEditor> addressesEditor = ListEditor .of(new EditorSource<AddressEditor>() { @Override public AddressEditor create(int index) { return new AddressEditor(); } }); } interface PersonWithListEditorDriver extends SimpleBeanEditorDriver<PersonWithList, PersonWithListEditor> { } class SimpleEditorWithDelegate<T> extends SimpleEditor<T> implements HasEditorDelegate<T> { EditorDelegate<T> delegate; public SimpleEditorWithDelegate(T value) { super(value); } public void setDelegate(EditorDelegate<T> delegate) { this.delegate = delegate; } } class ValueAwareAddressEditor extends AddressEditor implements ValueAwareEditor<Address> { int flushCalled; int setDelegateCalled; int setValueCalled; Address value; public void flush() { flushCalled++; } public void onPropertyChange(String... paths) { } public void setDelegate(EditorDelegate<Address> delegate) { setDelegateCalled++; } public void setValue(Address value) { setValueCalled++; this.value = value; } } /** * All the mix-ins. Not that this seems like a good idea... */ class ValueAwareLeafAddressEditor extends LeafAddressEditor implements ValueAwareEditor<Address> { int flushCalled; int setDelegateCalled; public void flush() { flushCalled++; } public void onPropertyChange(String... paths) { } public void setDelegate(EditorDelegate<Address> delegate) { setDelegateCalled++; } } Person person; Address personAddress; Person manager; long now; static final String UNINITIALIZED = "uninitialized"; @Override public String getModuleName() { return "com.google.gwt.editor.Editor"; } public void test() { PersonEditorDriver driver = GWT.create(PersonEditorDriver.class); PersonEditor editor = new PersonEditor(); driver.initialize(editor); driver.edit(person); assertEquals(now, editor.localTime.getValue().longValue()); assertEquals("Alice", editor.name.getValue()); assertEquals("City", editor.addressEditor.city.getValue()); assertEquals("Street", editor.addressEditor.street.getValue()); assertEquals("Bill", editor.managerName.getValue()); editor.localTime.setValue(now + 1); editor.name.setValue("Charles"); editor.addressEditor.city.setValue("Wootville"); editor.addressEditor.street.setValue("12345"); editor.managerName.setValue("David"); driver.flush(); assertEquals(now + 1, person.localTime); assertEquals("Charles", person.name); assertEquals("Wootville", person.address.city); assertEquals("12345", person.address.street); assertEquals("David", person.manager.name); } public void testAliasedEditors() { PersonEditorWithAliasedSubEditors editor = new PersonEditorWithAliasedSubEditors(); PersonEditorWithAliasedSubEditorsDriver driver = GWT.create(PersonEditorWithAliasedSubEditorsDriver.class); driver.initialize(editor); driver.edit(person); assertEquals("Alice", editor.e1.name.getValue()); assertEquals("Alice", editor.e2.name.getValue()); /* * Expecting that aliased editors will be editing disjoint sets of * properties, but we want to at least have a predictable behavior if two * editors are assigned to the same property. */ editor.e1.name.setValue("Should not see this"); driver.flush(); assertEquals("Alice", person.getName()); editor.e2.name.setValue("Should see this"); driver.flush(); assertEquals("Should see this", person.getName()); } public void testDelegatePath() { PersonEditorWithManagerNameWithDelegate editor = new PersonEditorWithManagerNameWithDelegate(); PersonEditorWithManagerNameWithDelegateDriver driver = GWT.create(PersonEditorWithManagerNameWithDelegateDriver.class); driver.initialize(editor); driver.edit(person); assertEquals("manager.name", editor.managerName.delegate.getPath()); } public void testEditorWithNullSubEditor() { PersonEditor editor = new PersonEditor(); editor.addressEditor = null; PersonEditorDriver driver = GWT.create(PersonEditorDriver.class); driver.initialize(editor); driver.edit(person); assertEquals("Alice", editor.name.getValue()); editor.name.setValue("New name"); driver.flush(); assertEquals("New name", person.getName()); /* * Verify that changing the editor structure without re-initializing is a * no-op. */ editor.name.setValue("Should see this"); editor.addressEditor = new AddressEditor(); editor.addressEditor.city.setValue("Should not see this"); driver.flush(); assertEquals("Should see this", person.getName()); assertEquals("City", person.getAddress().getCity()); // Re-initialize and check that flushing works again driver.initialize(editor); driver.edit(person); editor.addressEditor.city.setValue("Should see this"); driver.flush(); assertEquals("Should see this", person.getAddress().getCity()); } /** * Test the use of the IsEditor interface that allows a view object to * encapsulate its Editor. */ public void testIsEditorView() { PersonEditorWithAddressEditorView personEditor = new PersonEditorWithAddressEditorView(); PersonEditorWithAddressEditorViewDriver driver = GWT.create(PersonEditorWithAddressEditorViewDriver.class); testLeafAddressEditor(driver, personEditor, personEditor.addressEditor.asEditor(), true); } /** * Test the use of the IsEditor interface that allows a view object to * encapsulate its Editor as well as be an editor itself. */ public void testIsEditorViewWithCoEditorA() { PersonEditorWithCoAddressEditorView personEditor = new PersonEditorWithCoAddressEditorView(); PersonEditorWithCoAddressEditorViewDriver driver = GWT.create(PersonEditorWithCoAddressEditorViewDriver.class); testLeafAddressEditor(driver, personEditor, personEditor.addressEditor, true); } /** * Test the use of the IsEditor interface that allows a view object to * encapsulate its Editor as well as be an editor itself. */ public void testIsEditorViewWithCoEditorB() { PersonEditorWithCoAddressEditorView personEditor = new PersonEditorWithCoAddressEditorView(); PersonEditorWithCoAddressEditorViewDriver driver = GWT.create(PersonEditorWithCoAddressEditorViewDriver.class); testLeafAddressEditor(driver, personEditor, personEditor.addressEditor.asEditor(), true); } /** * We want to verify that the sub-editors of a LeafValueEditor are not * initialized. Additonally, we want to ensure that the instance returned from * the LVE is assigned into the owner type. */ public void testLeafValueEditorDeclaredInSlot() { PersonEditorWithLeafAddressEditor personEditor = new PersonEditorWithLeafAddressEditor(); PersonEditorWithLeafAddressEditorDriver driver = GWT.create(PersonEditorWithLeafAddressEditorDriver.class); LeafAddressEditor addressEditor = personEditor.addressEditor; testLeafAddressEditor(driver, personEditor, addressEditor, true); } /** * This tests a weird case where the user has put a LeafValueEditor into a * plain Editor field. Because the Editor system relies heavily on static type * information at compile time, this is not a supported configuration. */ public void testLeafValueEditorInPlainEditorSlot() { PersonEditorDriver driver = GWT.create(PersonEditorDriver.class); PersonEditor personEditor = new PersonEditor(); LeafAddressEditor addressEditor = new LeafAddressEditor(); // Runtime assignment of unexpected LeafValueEditor personEditor.addressEditor = addressEditor; testLeafAddressEditor(driver, personEditor, addressEditor, true); } public void testLifecycle() { PersonEditorDriver driver = GWT.create(PersonEditorDriver.class); try { driver.edit(person); fail("Should have thrown execption"); } catch (IllegalStateException expected) { } driver.initialize(new PersonEditor()); try { driver.flush(); fail("Should have thrown exception"); } catch (IllegalStateException expected) { } driver.edit(person); driver.flush(); } /** * Test a top-level ListEditor. */ public void testListEditor() { final SortedMap<Integer, SimpleEditor<String>> positionMap = new TreeMap<Integer, SimpleEditor<String>>(); @SuppressWarnings("unchecked") final SimpleEditor<String>[] disposed = new SimpleEditor[1]; class StringSource extends EditorSource<SimpleEditor<String>> { public SimpleEditor<String> create(int index) { SimpleEditor<String> editor = SimpleEditor.of(); positionMap.put(index, editor); return editor; } public void dispose(SimpleEditor<String> editor) { disposed[0] = editor; positionMap.values().remove(editor); } public void setIndex(SimpleEditor<String> editor, int index) { positionMap.values().remove(editor); positionMap.put(index, editor); } }; ListEditorDriver driver = GWT.create(ListEditorDriver.class); ListEditor<String, SimpleEditor<String>> editor = ListEditor.of(new StringSource()); driver.initialize(editor); List<String> rawData = new ArrayList<String>(Arrays.asList("foo", "bar", "baz")); driver.edit(rawData); List<SimpleEditor<String>> editors = editor.getEditors(); assertEquals(rawData.size(), editors.size()); assertEquals(rawData, Arrays.asList(editors.get(0).getValue(), editors.get(1).getValue(), editors.get(2).getValue())); assertEquals(editors, new ArrayList<SimpleEditor<String>>(positionMap.values())); List<String> mutableList = editor.getList(); assertEquals(rawData, mutableList); // Test through wrapped list mutableList.set(1, "Hello"); assertEquals("Hello", editors.get(1).getValue()); // Test using editor editors.get(2).setValue("World"); assertEquals("baz", rawData.get(2)); driver.flush(); assertEquals("World", rawData.get(2)); // Change list size mutableList.add("quux"); assertEquals(4, editors.size()); assertEquals("quux", editors.get(3).getValue()); assertEquals(editors, new ArrayList<SimpleEditor<String>>(positionMap.values())); // Delete an element SimpleEditor<String> expectedDisposed = editors.get(0); mutableList.remove(0); assertSame(expectedDisposed, disposed[0]); assertEquals(3, editors.size()); assertEquals("quux", editors.get(2).getValue()); assertEquals(editors, new ArrayList<SimpleEditor<String>>(positionMap.values())); } /** * Ensure that a ListEditor deeper in the chain is properly flushed. */ public void testListEditorChainFlush() { PersonWithListEditorDriver driver = GWT.create(PersonWithListEditorDriver.class); PersonWithList person = new PersonWithList(); Address a1 = new Address(); a1.city = "a1City"; a1.street = "a1Street"; Address a2 = new Address(); a2.city = "a2City"; a2.street = "a2Street"; person.addresses.add(a1); person.addresses.add(a2); PersonWithListEditor personEditor = new PersonWithListEditor(); // Initialize driver.initialize(personEditor); try { personEditor.addressesEditor.getEditors(); // Address editors aren't created until edit() is called fail(); } catch (IllegalStateException expected) { } // Edit driver.edit(person); AddressEditor addressEditor = personEditor.addressesEditor.getEditors().get(1); assertEquals("a2City", addressEditor.city.getValue()); addressEditor.city.setValue("edited"); // Flush driver.flush(); assertEquals("edited", person.addresses.get(1).getCity()); } public void testMultipleBinding() { PersonEditorWithMultipleBindingsDriver driver = GWT.create(PersonEditorWithMultipleBindingsDriver.class); PersonEditorWithMultipleBindings editor = new PersonEditorWithMultipleBindings(); driver.initialize(editor); driver.edit(person); assertEquals("City", editor.one.city.getValue()); assertEquals("Street", editor.two.street.getValue()); editor.one.city.setValue("Foo"); editor.two.street.setValue("Bar"); driver.flush(); assertEquals("Foo", person.getAddress().getCity()); assertEquals("Bar", person.getAddress().getStreet()); } public void testOptionalField() { PersonEditorWithOptionalAddressDriver driver = GWT.create(PersonEditorWithOptionalAddressDriver.class); person.address = null; AddressEditor delegate = new AddressEditor(); PersonEditorWithOptionalAddressEditor editor = new PersonEditorWithOptionalAddressEditor(delegate); driver.initialize(editor); // Make sure we don't blow up with the null field driver.edit(person); editor.address.setValue(personAddress); assertEquals("City", delegate.city.getValue()); delegate.city.setValue("Hello"); driver.flush(); assertNotNull(person.address); assertSame(personAddress, person.address); assertEquals("Hello", personAddress.city); editor.address.setValue(null); driver.flush(); assertNull(person.address); } public void testValueAwareEditorInDeclaredSlot() { PersonEditorWithValueAwareAddressEditorDriver driver = GWT.create(PersonEditorWithValueAwareAddressEditorDriver.class); PersonEditorWithValueAwareAddressEditor personEditor = new PersonEditorWithValueAwareAddressEditor(); ValueAwareAddressEditor addressEditor = personEditor.addressEditor; testValueAwareAddressEditor(driver, personEditor, addressEditor); } /** * This is another instance of a LeafValueEditor found in a plain slot. */ public void testValueAwareEditorInPlainSlot() { PersonEditorDriver driver = GWT.create(PersonEditorDriver.class); PersonEditor personEditor = new PersonEditor(); ValueAwareAddressEditor addressEditor = new ValueAwareAddressEditor(); // Runtime assignment of unexpected LeafValueEditor personEditor.addressEditor = addressEditor; testValueAwareAddressEditor(driver, personEditor, addressEditor); } public void testValueAwareLeafValueEditorInDeclaredSlot() { PersonEditorWithValueAwareLeafAddressEditor personEditor = new PersonEditorWithValueAwareLeafAddressEditor(); PersonEditorWithValueAwareLeafAddressEditorDriver driver = GWT.create(PersonEditorWithValueAwareLeafAddressEditorDriver.class); ValueAwareLeafAddressEditor addressEditor = personEditor.addressEditor; testLeafAddressEditor(driver, personEditor, addressEditor, true); assertEquals(1, addressEditor.flushCalled); assertEquals(1, addressEditor.setDelegateCalled); } public void testValueAwareLeafValueEditorInPlainEditorSlot() { PersonEditorDriver driver = GWT.create(PersonEditorDriver.class); PersonEditor personEditor = new PersonEditor(); ValueAwareLeafAddressEditor addressEditor = new ValueAwareLeafAddressEditor(); // Runtime assignment of unexpected LeafValueEditor personEditor.addressEditor = addressEditor; testLeafAddressEditor(driver, personEditor, addressEditor, true); assertEquals(1, addressEditor.flushCalled); assertEquals(1, addressEditor.setDelegateCalled); } @Override protected void gwtSetUp() throws Exception { personAddress = new Address(); personAddress.city = "City"; personAddress.street = "Street"; manager = new Person(); manager.name = "Bill"; person = new Person(); person.address = personAddress; person.name = "Alice"; person.manager = manager; person.localTime = now = System.currentTimeMillis(); } private <T extends Editor<Person>> void testLeafAddressEditor( SimpleBeanEditorDriver<Person, T> driver, T personEditor, LeafAddressEditor addressEditor, boolean declaredAsLeaf) { Address oldAddress = person.address; // Initialize driver.initialize(personEditor); assertEquals(0, addressEditor.setValueCalled); assertEquals(0, addressEditor.getValueCalled); // Edit driver.edit(person); assertEquals(1, addressEditor.setValueCalled); // The DirtCollector will interrogate the leaf editors assertEquals(1, addressEditor.getValueCalled); // Flush driver.flush(); assertEquals(1, addressEditor.setValueCalled); assertEquals(2, addressEditor.getValueCalled); assertNotSame(oldAddress, person.address); assertSame(person.address, addressEditor.value); } private <T extends Editor<Person>> void testValueAwareAddressEditor( SimpleBeanEditorDriver<Person, T> driver, T personEditor, ValueAwareAddressEditor addressEditor) { Address oldAddress = person.address; // Initialize driver.initialize(personEditor); assertEquals(0, addressEditor.setValueCalled); assertEquals(0, addressEditor.setDelegateCalled); assertEquals(0, addressEditor.flushCalled); // Edit driver.edit(person); assertEquals(1, addressEditor.setValueCalled); assertEquals(1, addressEditor.setDelegateCalled); assertEquals(0, addressEditor.flushCalled); assertEquals("City", addressEditor.city.getValue()); // Flush driver.flush(); assertEquals(1, addressEditor.setValueCalled); assertEquals(1, addressEditor.setDelegateCalled); assertEquals(1, addressEditor.flushCalled); assertSame(oldAddress, person.address); assertSame(person.address, addressEditor.value); } }