Package org.waveprotocol.wave.model.util

Source Code of org.waveprotocol.wave.model.util.DocumentEventRouterTestBase$DummyElm

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.util;

import static org.mockito.Matchers.same;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;

import junit.framework.TestCase;

import org.mockito.Matchers;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.waveprotocol.wave.model.document.Doc;
import org.waveprotocol.wave.model.document.DocHandler;
import org.waveprotocol.wave.model.document.ObservableDocument;
import org.waveprotocol.wave.model.document.ObservableMutableDocument;
import org.waveprotocol.wave.model.document.indexed.DocumentHandler;
import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema;
import org.waveprotocol.wave.model.document.util.DocumentEventRouter;
import org.waveprotocol.wave.model.document.util.ListenerRegistration;
import org.waveprotocol.wave.model.document.util.Point;
import org.waveprotocol.wave.model.document.util.XmlStringBuilder;
import org.waveprotocol.wave.model.testing.BasicFactories;

import java.util.Collections;
import java.util.Map;

/**
* Generic tests for event routers.
*
*/
public abstract class DocumentEventRouterTestBase extends TestCase {

  /**
   * An empty document.
   */
  private ObservableDocument realDoc;

  /**
   * A mock spying on the document.
   */
  private ObservableDocument doc;

  /**
   * The list of listeners currently registered on the document.
   */
  private final CopyOnWriteSet<Object> docListeners = CopyOnWriteSet.create();

  private Doc.E elmOne;
  private Doc.E elmTwo;

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    this.realDoc = createDocument();
    elmOne = realDoc.createChildElement(realDoc.getDocumentElement(), "a", noAttribs());
    elmTwo = realDoc.createChildElement(realDoc.getDocumentElement(), "b", noAttribs());
    this.doc = spy(realDoc);
    doAnswer(new Answer<Void>() {
      @SuppressWarnings("unchecked")
      @Override
      public Void answer(InvocationOnMock invocation) throws Throwable {
        DocumentHandler<Doc.N, Doc.E, Doc.T> listener =
              (DocumentHandler<Doc.N, Doc.E, Doc.T>) invocation.getArguments()[0];
        docListeners.add(listener);
        realDoc.addListener(listener);
        return null;
      }
    }).when(doc).addListener(Mockito.<DocHandler>any());
    doAnswer(new Answer<Void>() {
      @SuppressWarnings("unchecked")
      @Override
      public Void answer(InvocationOnMock invocation) throws Throwable {
        DocumentHandler<Doc.N, Doc.E, Doc.T> listener =
          (DocumentHandler<Doc.N, Doc.E, Doc.T>) invocation.getArguments()[0];
        docListeners.remove(invocation.getArguments()[0]);
        realDoc.removeListener(listener);
        return null;
      }
    }).when(doc).removeListener(Mockito.<DocHandler>any());
    // Mockito messes with how this works on the mock for some reason so we stub
    // it here to get the right behavior.
    doAnswer(new Answer<Point<Doc.N>>() {
      @Override
      public Point<Doc.N> answer(InvocationOnMock invocation) throws Throwable {
        return realDoc.locate((Integer) invocation.getArguments()[0]);
      }
    }).when(doc).locate(Mockito.anyInt());
  }

  protected abstract DocumentEventRouter<Doc.N, Doc.E, ?> createRouter(
      ObservableMutableDocument<Doc.N, Doc.E, Doc.T> doc);

  /**
   * Create an empty document.  Subclasses can override this if they need a
   * certain kind of document.
   */
  protected ObservableDocument createDocument() {
    return BasicFactories.createDocument(DocumentSchema.NO_SCHEMA_CONSTRAINTS);
  }

  /**
   * Test that just one listener is registered per router and only when the router
   * has active listeners.
   */
  @SuppressWarnings("unchecked")
  public void testAddedOnDemand() {
    assertEquals(0, docListeners.size());
    DocumentEventRouter<Doc.N, Doc.E, ?> router = createRouter(doc);
    assertEquals(0, docListeners.size());
    ListenerRegistration firstReg = router.addAttributeListener(new DummyElm(),
        mock(AttributeListener.class));
    assertEquals(1, docListeners.size());
    ListenerRegistration secondReg = router.addAttributeListener(new DummyElm(),
        mock(AttributeListener.class));
    assertEquals(1, docListeners.size());
    firstReg.detach();
    assertEquals(1, docListeners.size());
    secondReg.detach();
    assertEquals(0, docListeners.size());
  }

  /**
   * Test that removing a node removes all associated listeners and, if possible,
   * unregisters the router from the document.
   */
  @SuppressWarnings("unchecked")
  public void testRemoveListenersWhenDeleted() {
    assertEquals(0, docListeners.size());
    DocumentEventRouter<Doc.N, Doc.E, ?> router = createRouter(doc);
    assertEquals(0, docListeners.size());
    router.addAttributeListener(elmOne, mock(AttributeListener.class));
    router.addAttributeListener(elmOne, mock(AttributeListener.class));
    router.addDeletionListener(elmOne, mock(DeletionListener.class));
    router.addDeletionListener(elmOne, mock(DeletionListener.class));
    router.addChildListener(elmOne, mock(ElementListener.class));
    router.addChildListener(elmOne, mock(ElementListener.class));
    assertEquals(1, docListeners.size());
    realDoc.deleteNode(elmOne);
    assertEquals(0, docListeners.size());
  }

  /**
   * Test that removing yourself during event handling works as expected.
   */
  public void testSelfRemoval() {
    class SelfRemovalListener implements ElementListener<Doc.E> {
      private int removeCallCount = 0;
      private ListenerRegistration reg;
      @Override
      public void onElementAdded(Doc.E element) {
        fail();
      }
      @Override
      public void onElementRemoved(Doc.E element) {
        removeCallCount++;
        if (removeCallCount == 1) {
          reg.detach();
        }
      }
    }
    Doc.E parent = elmOne;
    Doc.E childOne = realDoc.createChildElement(parent, "a", noAttribs());
    Doc.E childTwo = realDoc.createChildElement(parent, "b", noAttribs());
    DocumentEventRouter<Doc.N, Doc.E, ?> router = createRouter(doc);
    SelfRemovalListener util = new SelfRemovalListener();
    util.reg = router.addChildListener(parent, util);
    assertEquals(0, util.removeCallCount);
    realDoc.deleteNode(childTwo);
    assertEquals(1, util.removeCallCount);
    realDoc.deleteNode(childOne);
    assertEquals(1, util.removeCallCount);
  }

  /**
   * Test that attribute and deletion listeners only receive events for the elements
   * they're listening to.
   */
  @SuppressWarnings("unchecked")
  public void testFiltering() {
    DocumentEventRouter<Doc.N, Doc.E, ?> router = createRouter(doc);
    AttributeListener<Doc.E> attribListenerOne = mock(AttributeListener.class);
    router.addAttributeListener(elmOne, attribListenerOne);
    AttributeListener<Doc.E> attribListenerTwo = mock(AttributeListener.class);
    router.addAttributeListener(elmTwo, attribListenerTwo);
    realDoc.setElementAttribute(elmOne, "t", "1");
    verify(attribListenerOne).onAttributesChanged(same(elmOne), anyAttribs(), anyAttribs());
    verify(attribListenerTwo, never()).onAttributesChanged(anyElement(), anyAttribs(), anyAttribs());
    realDoc.setElementAttribute(elmTwo, "s", "2");
    Mockito.verify(attribListenerOne).onAttributesChanged(same(elmOne), anyAttribs(), anyAttribs());
    Mockito.verify(attribListenerTwo).onAttributesChanged(same(elmTwo), anyAttribs(), anyAttribs());
    DeletionListener deleteListenerOne = mock(DeletionListener.class);
    router.addDeletionListener(elmOne, deleteListenerOne);
    DeletionListener deleteListenerTwo = mock(DeletionListener.class);
    router.addDeletionListener(elmTwo, deleteListenerTwo);
    realDoc.deleteNode(elmTwo);
    verify(deleteListenerOne, never()).onDeleted();
    verify(deleteListenerTwo).onDeleted();
    realDoc.deleteNode(elmOne);
    verify(deleteListenerOne).onDeleted();
    verify(deleteListenerTwo).onDeleted();
    verify(attribListenerOne).onAttributesChanged(same(elmOne), anyAttribs(), anyAttribs());
    verify(attribListenerTwo).onAttributesChanged(same(elmTwo), anyAttribs(), anyAttribs());
    verifyNoMoreInteractions(deleteListenerOne);
    verifyNoMoreInteractions(deleteListenerTwo);
    verifyNoMoreInteractions(attribListenerOne);
    verifyNoMoreInteractions(attribListenerTwo);
  }

  /**
   * Test that child listeners are called as appropriate.
   */
  @SuppressWarnings("unchecked")
  public void testChildListeners() {
    DocumentEventRouter<Doc.N, Doc.E, ?> router = createRouter(doc);
    ElementListener<Doc.E> elmListenerOne = mock(ElementListener.class);
    ElementListener<Doc.E> elmListenerTwo = mock(ElementListener.class);
    router.addChildListener(elmOne, elmListenerOne);
    router.addChildListener(elmTwo, elmListenerTwo);
    Doc.E childOne = realDoc.createChildElement(elmOne, "x", noAttribs());
    verify(elmListenerOne).onElementAdded(childOne);
    verify(elmListenerTwo, never()).onElementAdded(anyElement());
    Doc.E childTwo = realDoc.createChildElement(elmTwo, "y", noAttribs());
    verify(elmListenerOne).onElementAdded(childOne);
    verify(elmListenerTwo).onElementAdded(childTwo);
    realDoc.deleteNode(childTwo);
    verify(elmListenerOne, never()).onElementRemoved(anyElement());
    verify(elmListenerTwo).onElementRemoved(childTwo);
    realDoc.deleteNode(childOne);
    verify(elmListenerOne).onElementRemoved(childOne);
    verify(elmListenerTwo).onElementRemoved(childTwo);
    verifyNoMoreInteractions(elmListenerOne);
    verifyNoMoreInteractions(elmListenerTwo);
  }

  /**
   * Test that only the root of a deletion is given child notifications, not
   * children of parents that are themselves being deleted.
   */
  @SuppressWarnings("unchecked")
  public void testRecursiveDeletion() {
    Doc.E parent = elmOne;
    final Doc.E child = realDoc.createChildElement(parent, "c", noAttribs());
    Doc.E grandChild = realDoc.createChildElement(child, "d", noAttribs());
    DocumentEventRouter<Doc.N, Doc.E, ?> router = createRouter(doc);
    ElementListener<Doc.E> childRemovedListener = mock(ElementListener.class);
    router.addChildListener(parent, childRemovedListener);
    ElementListener<Doc.E> grandChildRemovedListener = mock(ElementListener.class);
    router.addChildListener(child, grandChildRemovedListener);
    realDoc.deleteNode(child);
    verify(childRemovedListener).onElementRemoved(same(child));
    verifyNoMoreInteractions(childRemovedListener);
    verifyZeroInteractions(grandChildRemovedListener);
  }

  /**
   * Test that only the root of an insertion is given child notifications.
   */
  @SuppressWarnings("unchecked") // Generic mocks.
  public void testRecursiveInsertion() {
    final Doc.E parent = elmOne;
    final DocumentEventRouter<Doc.N, Doc.E, ?> router = createRouter(doc);
    final ElementListener<Doc.E> childListener = mock(ElementListener.class);
    ElementListener<Doc.E> parentListener = mock(ElementListener.class);
    final ElementListener<Doc.E> secondParentListener = mock(ElementListener.class);
    doAnswer(new Answer<Void>() {
      public Void answer(InvocationOnMock invocation) throws Throwable {
        Doc.E child = (Doc.E) invocation.getArguments()[0];
        router.addChildListener(child, childListener);
        router.addChildListener(parent, secondParentListener);
        return null;
      }
    }).when(parentListener).onElementAdded(anyElement());
    router.addChildListener(parent, parentListener);
    Doc.E child = doc.insertXml(Point.start(doc, parent),
        XmlStringBuilder.createEmpty().wrap("a").wrap("b"));
    verify(parentListener).onElementAdded(child);
    verifyNoMoreInteractions(parentListener);
    verifyZeroInteractions(childListener);
    verifyZeroInteractions(secondParentListener);
  }

  private static Doc.E anyElement() {
    return Matchers.<Doc.E>any();
  }

  private static Map<String, String> anyAttribs() {
    return Matchers.<Map<String, String>>any();
  }

  private static Map<String, String> noAttribs() {
    return Collections.<String, String>emptyMap();
  }

  private static class DummyElm implements Doc.E { }

}
TOP

Related Classes of org.waveprotocol.wave.model.util.DocumentEventRouterTestBase$DummyElm

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.