/** * Copyright 2009 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 org.waveprotocol.wave.model.adt.docbased; import junit.framework.TestCase; import org.waveprotocol.wave.model.adt.ObservableStructuredValue; import org.waveprotocol.wave.model.adt.docbased.TestUtil.ValueContext; import org.waveprotocol.wave.model.document.ObservableMutableDocument; import org.waveprotocol.wave.model.document.util.DefaultDocumentEventRouter; import org.waveprotocol.wave.model.testing.BasicFactories; import org.waveprotocol.wave.model.testing.ExtraAsserts; import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.Pair; import org.waveprotocol.wave.model.util.Serializer; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; /** * Tests for the document-based structured value. * * @author anorth@google.com (Alex North) */ public class DocumentBasedStructuredValueTest extends TestCase { /** Key type for testing. */ private static enum Key { NAME1, NAME2 } private static final String CONTAINER_TAG = "container"; /** * An observer which allows setting and checking of expected events. */ private static class MockStructuredValueObserver implements ObservableStructuredValue.Listener<Key, Integer> { Queue<Pair<Map<Key, Integer>, Map<Key, Integer>>> expectations = new LinkedList<Pair<Map<Key, Integer>, Map<Key, Integer>>>(); public void expectValuesChanged(Map<Key, Integer> oldValues, Map<Key, Integer> newValues) { expectations.add(Pair.of(oldValues, newValues)); } public void expectDeletion() { expectations.add(null); } public void checkExpectationsSatisfied() { assertTrue(expectations.isEmpty()); } @Override public void onValuesChanged(Map<Key, ? extends Integer> oldValues, Map<Key, ? extends Integer> newValues) { Pair<Map<Key, Integer>, Map<Key, Integer>> expected = expectations.remove(); assertEquals(expected.first, oldValues); assertEquals(expected.second, newValues); } public void onDeleted() { assertNull(expectations.remove()); } } /** Value under test. */ private ObservableStructuredValue<Key, Integer> value; private ValueContext<?, ?> context; public void testInitialiser() { Initializer init = DocumentBasedStructuredValue.createInitialiser(Serializer.INTEGER, CollectionUtils.immutableMap(Key.NAME1, 23, Key.NAME2, 42)); TestUtil.assertInitializerValues(CollectionUtils.immutableMap(Key.NAME1.toString(), "23", Key.NAME2.toString(), "42"), init); init = DocumentBasedStructuredValue.createInitialiser(Serializer.INTEGER, CollectionUtils.immutableMap(Key.NAME1, (Integer) null)); TestUtil.assertInitializerValues(Collections.<String, String> emptyMap(), init); } public void testEmptyAttributeIsNull() { createEmptyTarget(); assertNull(value.get(Key.NAME1)); } // Getting and setting. // Each test involving set() is performed once with set(String, String) // and once with set(Map). public void testSetOnEmptyStateIsReturnedByGet() { // set(String, String). createEmptyTarget(); value.set(Key.NAME1, 42); assertEquals(new Integer(42), value.get(Key.NAME1)); assertNull(value.get(Key.NAME2)); // set(Map). createEmptyTarget(); value.set(CollectionUtils.immutableMap(Key.NAME1, 42)); assertEquals(new Integer(42), value.get(Key.NAME1)); assertNull(value.get(Key.NAME2)); } public void testSetOnEmptyStateInsertsIntoSubstrate() { // set(String, String). createEmptyTarget(); value.set(Key.NAME1, 10); assertSubstrateEquals(CollectionUtils.immutableMap(Key.NAME1, 10)); value.set(Key.NAME2, 20); assertSubstrateEquals(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20)); // set(Map). createEmptyTarget(); value.set(CollectionUtils.immutableMap(Key.NAME1, 10)); assertSubstrateEquals(CollectionUtils.immutableMap(Key.NAME1, 10)); value.set(CollectionUtils.immutableMap(Key.NAME2, 20)); assertSubstrateEquals(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20)); } public void testSubstrateValueIsReflected() { createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10)); assertEquals(new Integer(10), value.get(Key.NAME1)); assertNull(value.get(Key.NAME2)); } public void testSetReplacesValue() { // set(String, String). createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20)); value.set(Key.NAME1, 5); assertEquals(new Integer(5), value.get(Key.NAME1)); assertEquals(new Integer(20), value.get(Key.NAME2)); assertSubstrateEquals(CollectionUtils.immutableMap(Key.NAME1, 5, Key.NAME2, 20)); // set(Map). createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20)); value.set(CollectionUtils.immutableMap(Key.NAME1, 5)); assertEquals(new Integer(5), value.get(Key.NAME1)); assertEquals(new Integer(20), value.get(Key.NAME2)); assertSubstrateEquals(CollectionUtils.immutableMap(Key.NAME1, 5, Key.NAME2, 20)); } public void testSetNullClearsValue() { // set(String, String). createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10)); value.set(Key.NAME1, null); assertNull(value.get(Key.NAME1)); assertSubstrateEquals(new HashMap<Key, Integer>()); // set(Map). createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10)); value.set(CollectionUtils.immutableMap(Key.NAME1, (Integer) null)); assertNull(value.get(Key.NAME1)); assertSubstrateEquals(new HashMap<Key, Integer>()); } public void testRemoteSetReplacesValue() { createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20)); setValue(Key.NAME1, 5); assertEquals(new Integer(5), value.get(Key.NAME1)); assertEquals(new Integer(20), value.get(Key.NAME2)); assertSubstrateEquals(CollectionUtils.immutableMap(Key.NAME1, 5, Key.NAME2, 20)); } // Events. public void testDeletionProducesEvent() { createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20)); MockStructuredValueObserver observer = new MockStructuredValueObserver(); value.addListener(observer); observer.expectDeletion(); context.delete(); } public void testSetSingleFromNullProducesEvent() { createEmptyTarget(); MockStructuredValueObserver observer = new MockStructuredValueObserver(); value.addListener(observer); observer.expectValuesChanged(CollectionUtils.immutableMap(Key.NAME1, (Integer) null), CollectionUtils.immutableMap(Key.NAME1, 42)); value.set(Key.NAME1, 42); observer.checkExpectationsSatisfied(); } public void testSetNullProducesEvent() { createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20)); MockStructuredValueObserver observer = new MockStructuredValueObserver(); value.addListener(observer); observer.expectValuesChanged(CollectionUtils.immutableMap(Key.NAME1, 10), CollectionUtils.immutableMap(Key.NAME1, (Integer) null)); value.set(Key.NAME1, null); observer.checkExpectationsSatisfied(); } public void testSetManyProducesOneEvent() { createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20)); MockStructuredValueObserver observer = new MockStructuredValueObserver(); value.addListener(observer); observer.expectValuesChanged(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20), CollectionUtils.immutableMap(Key.NAME1, 5, Key.NAME2, 7)); value.set(CollectionUtils.immutableMap(Key.NAME1, 5, Key.NAME2, 7)); observer.checkExpectationsSatisfied(); } public void testRemoteSetManyProducesOneEvent() { createTargetOn(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20)); MockStructuredValueObserver observer = new MockStructuredValueObserver(); value.addListener(observer); observer.expectValuesChanged(CollectionUtils.immutableMap(Key.NAME1, 10, Key.NAME2, 20), CollectionUtils.immutableMap(Key.NAME1, 5, Key.NAME2, 7)); setValues(CollectionUtils.immutableMap(Key.NAME1, 5, Key.NAME2, 7)); observer.checkExpectationsSatisfied(); } // Helpers. /** * Initialises a target element representing an empty value. */ private void createEmptyTarget() { createTargetOn(Collections.<Key, Integer> emptyMap()); } /** * Initialises a target element with preset values. */ private void createTargetOn(Map<Key, Integer> values) { context = substrate(values); value = createTargetOn(context); } private static <N> ObservableStructuredValue<Key, Integer> createTargetOn( ValueContext<N, ?> context) { return createTargetOn2(context); } private static <N, E extends N> ObservableStructuredValue<Key, Integer> createTargetOn2( ValueContext<N, E> context) { return DocumentBasedStructuredValue.create(DefaultDocumentEventRouter.create(context.doc), context.container, Serializer.INTEGER, Key.class); } /** * Creates a substrate TODO based on a list of entries. * * @return a map-context view of the document state */ private static ValueContext<?, ?> substrate(Map<Key, Integer> values) { return substrate(BasicFactories.observableDocumentProvider().create("tagname", Collections.<String, String>emptyMap()), values); } /** * Populates a document with an initial map state TODO defined by an entry list. * * @return a map-context view of the document state. */ private static <N, E extends N> ValueContext<N, E> substrate( ObservableMutableDocument<N, E, ?> doc, Map<Key, Integer> values) { // Insert container element. E container = doc.createChildElement(doc.getDocumentElement(), CONTAINER_TAG, Collections.<String,String>emptyMap()); // Set attributes. for (Map.Entry<Key, Integer> entry : values.entrySet()) { doc.setElementAttribute(container, entry.getKey().toString(), Serializer.INTEGER.toString(entry.getValue())); } return new ValueContext<N, E>(doc, container); } /** * Sets the substrate value for a field. */ private void setValue(Key name, Integer value) { setValue1(context, name, value); } private <N> void setValue1 (ValueContext<N, ?> context, Key name, Integer value) { setValue2(context, name, value); } private <N, E extends N> void setValue2(ValueContext<N, E> context, Key name, Integer value) { context.doc.setElementAttribute(context.container, name.toString(), Serializer.INTEGER.toString(value)); } /** * Sets the substrate value for a number of fields simultaneously. */ private void setValues(Map<Key, Integer> values) { setValues1(context, values); } private <N> void setValues1(ValueContext<N, ?> context, Map<Key, Integer> values) { setValues2(context, values); } private <N, E extends N> void setValues2(ValueContext<N, E> context, Map<Key, Integer> values) { Map<String, String> valueStrings = CollectionUtils.newHashMap(); for (Map.Entry<Key, Integer> entry : values.entrySet()) { valueStrings.put(entry.getKey().toString(), Serializer.INTEGER.toString(entry.getValue())); } context.doc.updateElementAttributes(context.container, valueStrings); } /** * Asserts that the test-target's document substrate is in an expected state. * * @param expected map of entries describing the expected state */ private void assertSubstrateEquals(Map<Key, Integer> expected) { ExtraAsserts.assertStructureEquivalent(substrate(expected).doc, context.doc); } }