http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeHashedVersionFactory.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeHashedVersionFactory.java
 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeHashedVersionFactory.java
new file mode 100644
index 0000000..e9d25b8
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeHashedVersionFactory.java
@@ -0,0 +1,45 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.id.WaveletName;
+import org.waveprotocol.wave.model.version.HashedVersion;
+import org.waveprotocol.wave.model.version.HashedVersionFactory;
+
+/**
+ * A hashed version factory which generates unsigned versions.
+ *
+ * @author ano...@google.com (Alex North)
+ */
+public final class FakeHashedVersionFactory implements HashedVersionFactory {
+
+  public static final HashedVersionFactory INSTANCE = new 
FakeHashedVersionFactory();
+
+  @Override
+  public HashedVersion createVersionZero(WaveletName waveletName) {
+    return HashedVersion.unsigned(0);
+  }
+
+  @Override
+  public HashedVersion create(byte[] appliedDeltaBytes, HashedVersion 
versionAppliedAt,
+      int opsApplied) {
+    return HashedVersion.unsigned(versionAppliedAt.getVersion() + opsApplied);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeIdGenerator.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeIdGenerator.java 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeIdGenerator.java
new file mode 100644
index 0000000..f46802c
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeIdGenerator.java
@@ -0,0 +1,44 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.id.IdGenerator;
+import org.waveprotocol.wave.model.id.IdGeneratorImpl;
+import org.waveprotocol.wave.model.id.IdGeneratorImpl.Seed;
+
+
+/**
+ * Id generator suitable for use in testing.
+ *
+ */
+public final class FakeIdGenerator {
+
+  // Prevent instantiation
+  private FakeIdGenerator() {}
+
+  public static IdGenerator create() {
+    return new IdGeneratorImpl("example.com", new Seed() {
+      @Override
+      public String get() {
+        return "seed";
+      }
+    });
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeSilentOperationSink.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeSilentOperationSink.java
 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeSilentOperationSink.java
new file mode 100644
index 0000000..43bbd34
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeSilentOperationSink.java
@@ -0,0 +1,69 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.operation.Operation;
+import org.waveprotocol.wave.model.operation.SilentOperationSink;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A place where you can get a concrete OperationSink.Silent for testing.
+ *
+ * @author zdw...@google.com (David Wang)
+ */
+public class FakeSilentOperationSink<T extends Operation<?>> implements 
SilentOperationSink<T> {
+  private LinkedList<T> ops = new LinkedList<T>();
+
+  /**
+   * For unit testing
+   * @return the most recently consumed op
+   */
+  public T getConsumedOp() {
+    int size = ops.size();
+    return (size == 0) ? null : (ops.get(size - 1));
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void consume(T op) {
+    ops.addLast(op);
+  }
+
+  /**
+   * Clears the list of saved operations.
+   */
+  public void clear() {
+    ops.clear();
+  }
+
+  /**
+   * Gets the list of operations consumed by this sink since it was last
+   * cleared.
+   *
+   * @return the ops, from first consumed through most recently consumed.
+   */
+  public List<T> getOps() {
+    return Collections.unmodifiableList(ops);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveView.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveView.java 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveView.java
new file mode 100644
index 0000000..5f74e11
--- /dev/null
+++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveView.java
@@ -0,0 +1,229 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.id.IdGenerator;
+import org.waveprotocol.wave.model.id.WaveId;
+import org.waveprotocol.wave.model.id.WaveletId;
+import org.waveprotocol.wave.model.operation.SilentOperationSink;
+import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
+import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext;
+import org.waveprotocol.wave.model.schema.SchemaProvider;
+import org.waveprotocol.wave.model.wave.ObservableWavelet;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+import org.waveprotocol.wave.model.wave.WaveViewListener;
+import org.waveprotocol.wave.model.wave.data.DocumentFactory;
+import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl;
+import org.waveprotocol.wave.model.wave.opbased.ObservableWaveView;
+import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;
+import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl;
+
+/**
+ * Dummy implementation of a wave view.
+ *
+ */
+public final class FakeWaveView implements ObservableWaveView, 
Factory<OpBasedWavelet> {
+
+  public final static class Builder {
+    private final SchemaProvider schemas;
+    private IdGenerator idGenerator;
+    private WaveId waveId;
+    private ParticipantId viewer;
+    private SilentOperationSink<? super WaveletOperation> sink;
+    private WaveViewImpl.WaveletConfigurator configurator;
+    private DocumentFactory<?> docFactory;
+
+    private Builder(SchemaProvider schemas) {
+      this.schemas = schemas;
+    }
+
+    public Builder with(DocumentFactory<?> docFactory) {
+      this.docFactory = docFactory;
+      return this;
+    }
+
+    public Builder with(IdGenerator idGenerator) {
+      this.idGenerator = idGenerator;
+      return this;
+    }
+
+    public Builder with(WaveId wid) {
+      this.waveId = wid;
+      return this;
+    }
+
+    public Builder with(ParticipantId viewer) {
+      this.viewer = viewer;
+      return this;
+    }
+
+    public Builder with(SilentOperationSink<? super WaveletOperation> sink) {
+      this.sink = sink;
+      return this;
+    }
+
+    public Builder with(WaveViewImpl.WaveletConfigurator configurator) {
+      this.configurator = configurator;
+      return this;
+    }
+
+    public FakeWaveView build() {
+      if (idGenerator == null) {
+        idGenerator = FakeIdGenerator.create();
+      }
+      if (waveId == null) {
+        waveId = idGenerator.newWaveId();
+      }
+      if (viewer == null) {
+        viewer = FAKE_PARTICIPANT;
+      }
+      if (sink == null) {
+        sink = SilentOperationSink.VOID;
+      }
+      if (configurator == null) {
+        configurator = WaveViewImpl.WaveletConfigurator.ADD_CREATOR;
+      }
+      if (docFactory == null) {
+        // Document factory that accepts output-sink registrations.
+        docFactory = FakeDocument.Factory.create(schemas);
+      }
+
+      // Wavelet factory that does all the work.
+      OpBasedWaveletFactory waveletFactory = OpBasedWaveletFactory // \u2620
+          .builder(schemas) // \u2620
+          .with(WaveletDataImpl.Factory.create(docFactory)) // \u2620
+          .with(sink) // \u2620
+          .with(viewer) // \u2620
+          .build();
+
+      // And the view implementation using that factory.
+      WaveViewImpl<OpBasedWavelet> view =
+          WaveViewImpl.create(waveletFactory, waveId, idGenerator, viewer, 
configurator);
+
+      return new FakeWaveView(waveletFactory, view);
+    }
+  }
+
+  private static final ParticipantId FAKE_PARTICIPANT = new 
ParticipantId("f...@example.com");
+
+  private final OpBasedWaveletFactory factory;
+  private final WaveViewImpl<? extends OpBasedWavelet> view;
+
+  /**
+   * Creates a wave view.
+   *
+   * @param factory  factory exposing testing hacks
+   * @param view     real view implementation
+   */
+  private FakeWaveView(OpBasedWaveletFactory factory, WaveViewImpl<? extends 
OpBasedWavelet> view) {
+    this.factory = factory;
+    this.view = view;
+  }
+
+  /**
+   * @return a builder for a fake wave view.
+   */
+  public static Builder builder(SchemaProvider schemas) {
+    return new Builder(schemas);
+  }
+
+  //
+  // Expose as basic wavelet factory for wavelet-specific tests.
+  //
+
+  @Override
+  public OpBasedWavelet create() {
+    return createWavelet();
+  }
+
+  //
+  // Testing hacks.
+  //
+
+  public MockParticipationHelper getLastAuthoriser() {
+    return factory.getLastAuthoriser();
+  }
+
+  public WaveletOperationContext.Factory getLastContextFactory() {
+    return factory.getLastContextFactory();
+  }
+
+  public OpBasedWavelet createWavelet(WaveletId id) {
+    return view.createWavelet(id);
+  }
+
+  public void removeWavelet(ObservableWavelet wavelet) {
+    view.removeWavelet(wavelet);
+  }
+
+  //
+  // Delegate view implementation to view.
+  //
+
+  @Override
+  public OpBasedWavelet createRoot() {
+    return view.createRoot();
+  }
+
+  @Override
+  public OpBasedWavelet createUserData() {
+    return view.createUserData();
+  }
+
+  @Override
+  public OpBasedWavelet createWavelet() {
+    return view.createWavelet();
+  }
+
+  @Override
+  public OpBasedWavelet getRoot() {
+    return view.getRoot();
+  }
+
+  @Override
+  public OpBasedWavelet getUserData() {
+    return view.getUserData();
+  }
+
+  @Override
+  public OpBasedWavelet getWavelet(WaveletId waveletId) {
+    return view.getWavelet(waveletId);
+  }
+
+  @Override
+  public Iterable<? extends OpBasedWavelet> getWavelets() {
+    return view.getWavelets();
+  }
+
+  @Override
+  public WaveId getWaveId() {
+    return view.getWaveId();
+  }
+
+  @Override
+  public void addListener(WaveViewListener listener) {
+    view.addListener(listener);
+  }
+
+  @Override
+  public void removeListener(WaveViewListener listener) {
+    view.removeListener(listener);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletDataListener.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletDataListener.java
 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletDataListener.java
new file mode 100644
index 0000000..b281a10
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletDataListener.java
@@ -0,0 +1,320 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.version.HashedVersion;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+import org.waveprotocol.wave.model.wave.data.BlipData;
+import org.waveprotocol.wave.model.wave.data.WaveletData;
+import org.waveprotocol.wave.model.wave.data.WaveletDataListener;
+
+/**
+ * Stub implementation of {@link WaveletDataListener}. Each notification method
+ * saves the passed parameters for later inspection by accessors.
+ *
+ * @author zdw...@google.com (David Wang)
+ */
+public class FakeWaveletDataListener implements WaveletDataListener {
+  /**
+   * The last participantId received from
+   * {@link #onParticipantAdded(WaveletData, ParticipantId)}
+   */
+  private ParticipantId participantAdded;
+
+  /**
+   * The last participantId received from
+   * {@link #onParticipantRemoved(WaveletData, ParticipantId)}
+   */
+  private ParticipantId participantRemoved;
+
+  /**
+   * The last blip received from {@link #onBlipDataAdded(WaveletData, 
BlipData)}
+   */
+  private BlipData blipDataAdded;
+
+  /**
+   * The last oldTitle received from {@link #onTitleChanged(WaveletData, 
String, String)}.
+   */
+  private String oldTitle;
+
+  /**
+   * The last newTitle received from {@link #onTitleChanged(WaveletData, 
String, String)}.
+   */
+  private String newTitle;
+
+  /**
+   * The last blip target received from any other onBlipXxx method.
+   */
+  private BlipData blipModified;
+
+  /**
+   * The last old modified time received by
+   * {@link #onLastModifiedTimeChanged(WaveletData, long, long)}
+   */
+  private long oldLastModifiedTime;
+
+  /**
+   * The last new modified time received by
+   * {@link #onLastModifiedTimeChanged(WaveletData, long, long)}
+   */
+  private long newLastModifiedTime;
+
+  /**
+   * The last contributor received by
+   * {@link #onBlipDataContributorAdded(WaveletData, BlipData, ParticipantId)}
+   */
+  private ParticipantId blipContributorAdded;
+
+  /**
+   * The last contributor received by
+   * {@link #onBlipDataContributorRemoved(WaveletData, BlipData, 
ParticipantId)}
+   */
+  private ParticipantId blipContributorRemoved;
+
+  /**
+   * The last old timestamp received by
+   * {@link #onBlipDataTimestampModified(WaveletData, BlipData, long, long)}
+   */
+  private long blipOldTimestamp;
+
+  /**
+   * The last new timestamp received by
+   * {@link #onBlipDataTimestampModified(WaveletData, BlipData, long, long)}
+   */
+  private long blipNewTimestamp;
+  /**
+   * The last old version received by
+   * {@link #onBlipDataVersionModified(WaveletData, BlipData, long, long)}
+   */
+  private long blipOldVersion;
+
+  /**
+   * The last new version received by
+   * {@link #onBlipDataVersionModified(WaveletData, BlipData, long, long)}
+   */
+  private long blipNewVersion;
+
+  private long oldVersion;
+  private long newVersion;
+
+  private HashedVersion oldHashedVersion;
+  private HashedVersion newHashedVersion;
+
+  @Override
+  public void onParticipantAdded(WaveletData wavelet, ParticipantId 
participantId) {
+    this.participantAdded = participantId;
+  }
+
+  @Override
+  public void onParticipantRemoved(WaveletData wavelet, ParticipantId 
participantId) {
+    this.participantRemoved = participantId;
+  }
+
+  @Override
+  public void onLastModifiedTimeChanged(WaveletData waveletData, long oldTime, 
long newTime) {
+    this.oldLastModifiedTime = oldTime;
+    this.newLastModifiedTime = newTime;
+  }
+
+  @Override
+  public void onVersionChanged(WaveletData wavelet, long oldVersion, long 
newVersion) {
+    this.oldVersion = oldVersion;
+    this.newVersion = newVersion;
+  }
+
+  @Override
+  public void onHashedVersionChanged(WaveletData waveletData, HashedVersion 
oldHashedVersion,
+      HashedVersion newHashedVersion) {
+    this.oldHashedVersion = oldHashedVersion;
+    this.newHashedVersion = newHashedVersion;
+  }
+
+  @Override
+  public void onBlipDataAdded(WaveletData waveletData, BlipData blip) {
+    this.blipDataAdded = blip;
+  }
+
+  @Override
+  public void onBlipDataContributorAdded(
+      WaveletData waveletData, BlipData blip, ParticipantId contributor) {
+    this.blipModified = blip;
+    this.blipContributorAdded = contributor;
+  }
+
+  @Override
+  public void onBlipDataContributorRemoved(
+      WaveletData waveletData, BlipData blip, ParticipantId contributor) {
+    this.blipModified = blip;
+    this.blipContributorRemoved = contributor;
+  }
+
+  @Override
+  public void onBlipDataTimestampModified(
+      WaveletData waveletData, BlipData blip, long oldTime, long newTime) {
+    this.blipModified = blip;
+    this.blipOldTimestamp = oldTime;
+    this.blipNewTimestamp = newTime;
+  }
+
+  @Override
+  public void onBlipDataVersionModified(
+      WaveletData waveletData, BlipData blip, long oldVersion, long 
newVersion) {
+    this.blipModified = blip;
+    this.blipOldVersion = oldVersion;
+    this.blipNewVersion = newVersion;
+  }
+
+  @Deprecated
+  @Override
+  public void onRemoteBlipDataContentModified(WaveletData waveletData, 
BlipData blip) {
+    this.blipModified = blip;
+  }
+
+  @Override
+  public void onBlipDataSubmitted(WaveletData waveletData, BlipData blip) {
+    this.blipModified = blip;
+  }
+
+  /**
+   * @return the last participantId received by
+   *         {@link #onParticipantAdded(WaveletData, ParticipantId)}
+   */
+  public ParticipantId getParticipantAdded() {
+    return participantAdded;
+  }
+
+  /**
+   * @return the last participantId received by
+   *         {@link #onParticipantRemoved(WaveletData, ParticipantId)}
+   */
+  public ParticipantId getParticipantRemoved() {
+    return participantRemoved;
+  }
+
+  /**
+   * @return the last blip received by {@link #onBlipDataAdded(WaveletData, 
BlipData)}
+   */
+  public BlipData getBlipDataAdded() {
+    return blipDataAdded;
+  }
+
+  /**
+   * @return the last blip received by any of the other onBlipDataXxx methods.
+   */
+  public BlipData getBlipModified() {
+    return blipModified;
+  }
+
+  /**
+   * @return the last newTitle received by
+   *         {@link #onTitleChanged(WaveletData, String, String)}.
+   */
+  public String getNewTitle() {
+    return newTitle;
+  }
+
+  /**
+   * @return the last oldTitle received by
+   *         {@link #onTitleChanged(WaveletData, String, String)}.
+   */
+  public String getOldTitle() {
+    return oldTitle;
+  }
+
+  /**
+   * @return the last old time received by
+   *         {@link #onLastModifiedTimeChanged(WaveletData, long, long)}
+   */
+  public long getOldLastModifiedTime() {
+    return oldLastModifiedTime;
+  }
+
+  /**
+   * @return the last new time received by
+   *         {@link #onLastModifiedTimeChanged(WaveletData, long, long)}
+   */
+  public long getNewLastModifiedTime() {
+    return newLastModifiedTime;
+  }
+
+  /**
+   * @return the last participant received by
+   *         {@link #onBlipDataContributorAdded(WaveletData, BlipData, 
ParticipantId)}
+   */
+  public ParticipantId getBlipContributorAdded() {
+    return blipContributorAdded;
+  }
+
+  /**
+   * @return the last participant receieved by
+   *         {@link #onBlipDataContributorRemoved(WaveletData, BlipData, 
ParticipantId)}
+   */
+  public ParticipantId getBlipContributorRemoved() {
+    return blipContributorRemoved;
+  }
+
+  /**
+   * @return the last old timestamp received by
+   *         {@link #onBlipDataTimestampModified(WaveletData, BlipData, long, 
long)}
+   */
+  public long getBlipOldTimestamp() {
+    return blipOldTimestamp;
+  }
+
+  /**
+   * @return the last new timestamp received by
+   *         {@link #onBlipDataTimestampModified(WaveletData, BlipData, long, 
long)}
+   */
+  public long getBlipNewTimestamp() {
+    return blipNewTimestamp;
+  }
+
+  /**
+   * @return the last new version received by
+   *         {@link #onBlipDataVersionModified(WaveletData, BlipData, long, 
long)}
+   */
+  public long getBlipOldVersion() {
+    return blipOldVersion;
+  }
+
+  /**
+   * @return the last old version received by
+   *         {@link #onBlipDataVersionModified(WaveletData, BlipData, long, 
long)}
+   */
+  public long getBlipNewVersion() {
+    return blipNewVersion;
+  }
+
+  public long getNewVersion() {
+    return newVersion;
+  }
+
+  public long getOldVersion() {
+    return oldVersion;
+  }
+
+  public HashedVersion getNewHashedVersion() {
+    return newHashedVersion;
+  }
+
+  public HashedVersion getOldHashedVersion() {
+    return oldHashedVersion;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletListener.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletListener.java
 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletListener.java
new file mode 100644
index 0000000..8d66614
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletListener.java
@@ -0,0 +1,58 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.wave.ObservableWavelet;
+import org.waveprotocol.wave.model.wave.WaveletListener;
+import org.waveprotocol.wave.model.wave.opbased.WaveletListenerImpl;
+
+import org.waveprotocol.wave.model.wave.ParticipantId;
+
+/**
+ * Stub implementation of {@link WaveletListener}.  Each notification method
+ * saves the passed parameters for later inspection by accessors.
+ *
+ * @author zdw...@google.com (David Wang)
+ */
+public class FakeWaveletListener extends WaveletListenerImpl {
+  /** The last participant received from 
+   * {@link #onParticipantAdded(ObservableWavelet, ParticipantId)}
+   */
+  private ParticipantId participant;
+
+  @Override
+  public void onParticipantAdded(ObservableWavelet wavelet, ParticipantId 
participant) {
+    this.participant = participant;
+  }
+
+  /**
+   * @return the last {@code participant} received by 
+   * {@link #onParticipantAdded(ObservableWavelet, ParticipantId)}.
+   */
+  public ParticipantId getParticipant() {
+    return participant;
+  }
+
+
+  /** Resets all fields. */
+  public void reset() {
+    this.participant = null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericGWTTestBase.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericGWTTestBase.java
 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericGWTTestBase.java
new file mode 100644
index 0000000..98ce42b
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericGWTTestBase.java
@@ -0,0 +1,77 @@
+/**
+ * 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.testing;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * The base class for running a model-related test case as a GWT test case.
+ *
+ * A {@link GenericGWTTestBase} contains a {@link GenericTestBase}, to which 
it forwards all
+ * relevant testing methods.  This base class holds the reference to the
+ * contained test, and forwards {@link GWTTestCase#gwtSetUp()} and
+ * {@link GWTTestCase#gwtTearDown()} to it.
+ *
+ * To run a vanilla JUnit test case as a GWTTestCase, simply write the JUnit
+ * test as an extension of {@link GenericTestBase}, and create a parallel 
extension
+ * of this class that wraps an instance of the plain test case, and forwards
+ * all test methods to it.
+ *
+ * @param <T> wrapped test case class
+ */
+public abstract class GenericGWTTestBase<T extends GenericTestBase<?>> extends 
GWTTestCase {
+  /** The wrapped vanilla test case. */
+  protected final T target;
+
+  /**
+   * The default constructor.
+   */
+  protected GenericGWTTestBase(T target) {
+    this.target = target;
+  }
+
+  /**
+   * Forwards to wrapped test's {@link GenericTestBase#setUp()}.
+   */
+  @Override
+  protected void gwtSetUp() throws Exception {
+    target.setUp();
+  }
+
+  /**
+   * Forwards to wrapped test's {@link GenericTestBase#tearDown()}.
+   */
+  @Override
+  protected void gwtTearDown() throws Exception {
+    target.tearDown();
+  }
+
+  /**
+   * Specifies a module to use when running this test case. The returned
+   * module must cause the source for this class to be included.
+   *
+   * @see com.google.gwt.junit.client.GWTTestCase#getModuleName()
+   */
+  @Override
+  public String getModuleName() {
+    return "org.waveprotocol.wave.model.tests";
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericTestBase.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericTestBase.java 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericTestBase.java
new file mode 100644
index 0000000..980a987
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericTestBase.java
@@ -0,0 +1,70 @@
+/**
+ * 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.testing;
+
+import junit.framework.TestCase;
+
+/**
+ * Generic base implementation for a test case that tests the behaviour of a
+ * single type.  This implementation holds a reference to a factory for
+ * creating instances of that interface, and uses that factory to instantiates
+ * the instance to test in {@link #setUp()}.
+ *
+ * @param <T> interface type being tested
+ */
+public abstract class GenericTestBase<T> extends TestCase {
+  /** Factory used to create each wave to be tested. */
+  protected final Factory<? extends T> factory;
+
+  // State initialized in setUp()
+
+  /** Target to test. */
+  protected T target;
+
+  /**
+   * Creates this test case, which runs on the wave-datas created by a factory.
+   *
+   * @param factory  factory for creating the wave-datas to test
+   */
+  protected GenericTestBase(Factory<? extends T> factory) {
+    this.factory = factory;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * This implementation uses the test's factory to creates a test target.
+   */
+  @Override
+  protected void setUp() {
+    target = factory.create();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected void tearDown() throws Exception {
+    // This is only overridden to expose tearDown to GWTTestBase (which should
+    // be in GWTTestBase's scope anyway, since it extends TestCase, but for
+    // some reason it isn't).
+    super.tearDown();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/MockParticipationHelper.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/MockParticipationHelper.java
 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/MockParticipationHelper.java
new file mode 100644
index 0000000..bca7824
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/MockParticipationHelper.java
@@ -0,0 +1,111 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.wave.ParticipantId;
+import org.waveprotocol.wave.model.wave.ParticipationHelper;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * Mock {@link ParticipationHelper}.
+ *
+ */
+public class MockParticipationHelper implements ParticipationHelper {
+
+  /**
+   * Frame used with {@link MockParticipationHelper} to record expectations
+   * and desired results.
+   */
+  public static class Frame {
+    private final Set<ParticipantId> candidates;
+    private final ParticipantId editor;
+    private final ParticipantId result;
+
+    /**
+     * Creates a frame that will either return a given participant or throw an
+     * {@link IllegalStateException} if no participant is given.
+     *
+     * @param result participant to return from this frame, or null if an
+     *        {@link IllegalStateException} should be thrown.
+     * @param editor required for this frame to apply.
+     * @param candidates required for this frame to apply.
+     */
+    public Frame(ParticipantId result, ParticipantId editor,
+        ParticipantId... candidates) {
+      this.result = result;
+      this.editor = editor;
+      this.candidates = new HashSet<ParticipantId>(Arrays.asList(candidates));
+    }
+
+    /** Returns the result or throws the exception dictated by this frame. */
+    public ParticipantId apply() {
+      if (result == null) {
+        throw new IllegalStateException("Authoriser set to throw exception on 
this frame.");
+      } else {
+        return result;
+      }
+    }
+
+    /** Checks whether the given arguments match those expected by this frame. 
*/
+    public boolean matches(ParticipantId editor, Set<ParticipantId> 
candidates) {
+      return editor.equals(this.editor) && candidates.equals(this.candidates);
+    }
+  }
+
+  private final LinkedList<Frame> frames = new LinkedList<Frame>();
+
+  /**
+   * {@inheritDoc}
+   *
+   * Makes a decision by comparing against the next frame in the stub. If
+   * successful, that frame will then be discarded.
+   *
+   * @return the return participant of the frame if the arguments match those 
of
+   *         the frame and the frame includes a return participant.
+   * @throws IllegalStateException if the arguments match those of the frame 
and
+   *         the frame is designed to throw such an exception.
+   * @throws AssertionError if the arguments do not match those of the frame.
+   * @throws NoSuchElementException if there are no frames left.
+   */
+  @Override
+  public ParticipantId getAuthoriser(ParticipantId editor, Set<ParticipantId> 
candidates) {
+    if (frames.isEmpty()) {
+      throw new NoSuchElementException("No frames left to compare with 
getAuthoriser("
+          + editor + ", " + candidates + ")");
+    } else {
+      Frame frame = frames.removeFirst();
+      if (frame.matches(editor, candidates)) {
+        return frame.apply();
+      } else {
+        throw new AssertionError();
+      }
+    }
+  }
+
+  /** Adds a given frame to the end of the list of those expected by this 
stub. */
+  public void program(Frame frame) {
+    frames.addLast(frame);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/MockWaveletOperationContextFactory.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/MockWaveletOperationContextFactory.java
 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/MockWaveletOperationContextFactory.java
new file mode 100644
index 0000000..df78ca4
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/MockWaveletOperationContextFactory.java
@@ -0,0 +1,61 @@
+/**
+ * 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.testing;
+
+import 
org.waveprotocol.wave.model.operation.wave.AbstractWaveletOperationContextFactory;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+
+/**
+ * WaveletOperationContext.Factory that supports setting the timestamp
+ * and default participant id to use.
+ *
+ */
+public class MockWaveletOperationContextFactory extends 
AbstractWaveletOperationContextFactory {
+  private long timeMillis;
+  private ParticipantId participantId;
+
+  @Override
+  protected long currentTimeMillis() {
+    return timeMillis;
+  }
+
+  @Override
+  public ParticipantId getParticipantId() {
+    return participantId;
+  }
+
+  /**
+   * Sets the timestamp for future WaveletOperationContext objects generated
+   * by this factory.
+   */
+  public MockWaveletOperationContextFactory setCurrentTimeMillis(long 
timeMillis) {
+    this.timeMillis = timeMillis;
+    return this;
+  }
+
+  /**
+   * Sets the participant for future WaveletOperationContext objects generated
+   * by this factory.
+   */
+  public MockWaveletOperationContextFactory setParticipantId(ParticipantId 
participantId) {
+    this.participantId = participantId;
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/ModelTestUtils.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/ModelTestUtils.java 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/ModelTestUtils.java
new file mode 100644
index 0000000..8df4800
--- /dev/null
+++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/ModelTestUtils.java
@@ -0,0 +1,61 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.document.operation.DocInitialization;
+import org.waveprotocol.wave.model.document.operation.impl.AttributesImpl;
+import 
org.waveprotocol.wave.model.document.operation.impl.DocInitializationBuilder;
+
+/**
+ * A utility class containing convenient methods for creating and checking blip
+ * document content.
+ *
+ */
+public final class ModelTestUtils {
+
+  private ModelTestUtils() {
+  }
+
+  /**
+   * Creates a document with the given content.
+   *
+   * @param contentText The content that the document should have.
+   * @return The document with the given content.
+   */
+  public static DocInitialization createContent(String contentText) {
+    if (contentText.isEmpty()) {
+      return (new DocInitializationBuilder())
+          .elementStart("body", new AttributesImpl())
+          .elementStart("line", new AttributesImpl())
+          .elementEnd()
+          .elementEnd()
+          .build();
+    } else {
+      return new DocInitializationBuilder()
+          .elementStart("body", new AttributesImpl())
+          .elementStart("line", new AttributesImpl())
+          .elementEnd()
+          .characters(contentText)
+          .elementEnd()
+          .build();
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/OpBasedWaveletFactory.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/OpBasedWaveletFactory.java
 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/OpBasedWaveletFactory.java
new file mode 100644
index 0000000..a2011bf
--- /dev/null
+++ 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/OpBasedWaveletFactory.java
@@ -0,0 +1,194 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.id.IdGenerator;
+import org.waveprotocol.wave.model.id.WaveId;
+import org.waveprotocol.wave.model.id.WaveletId;
+import org.waveprotocol.wave.model.operation.OperationException;
+import org.waveprotocol.wave.model.operation.OperationRuntimeException;
+import org.waveprotocol.wave.model.operation.SilentOperationSink;
+import org.waveprotocol.wave.model.operation.SilentOperationSink.Executor;
+import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
+import org.waveprotocol.wave.model.schema.SchemaProvider;
+import org.waveprotocol.wave.model.version.HashedVersion;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+import org.waveprotocol.wave.model.wave.data.DocumentFactory;
+import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
+import org.waveprotocol.wave.model.wave.data.WaveletData;
+import org.waveprotocol.wave.model.wave.data.impl.EmptyWaveletSnapshot;
+import 
org.waveprotocol.wave.model.wave.data.impl.ObservablePluggableMutableDocument;
+import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl;
+import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;
+import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl;
+
+/**
+ * Factory for creating {@link OpBasedWavelet} instances suitable for testing.
+ *
+ */
+public final class OpBasedWaveletFactory implements 
WaveViewImpl.WaveletFactory<OpBasedWavelet>,
+    Factory<OpBasedWavelet> {
+
+  /**
+   * An operation sink that, on every operation it consumes, fires a version
+   * update operation back to the wavelet, then passes the operation along to
+   * the next sink. Wavelet versioning is specifically designed to be
+   * server-controlled. In a test context, this sink is used to simulate the
+   * behaviour of a wavelet server firing back acknowledgements with version
+   * updates, in order that tests that mutate wavelets also see version number
+   * increase.
+   */
+  private final static class VersionIncrementingSink implements
+      SilentOperationSink<WaveletOperation> {
+    private final WaveletData data;
+    private final SilentOperationSink<? super WaveletOperation> output;
+
+    public VersionIncrementingSink(WaveletData data,
+        SilentOperationSink<? super WaveletOperation> output) {
+      this.data = data;
+      this.output = output;
+    }
+
+    @Override
+    public void consume(WaveletOperation op) {
+      // Update local version, simulating server response.
+      try {
+        op.createVersionUpdateOp(1, null).apply(data);
+      } catch (OperationException e) {
+        throw new OperationRuntimeException("test sink verison update failed", 
e);
+      }
+
+      // Pass to output sink.
+      output.consume(op);
+    }
+  }
+
+  /**
+   * Builder, through which a factory can be conveniently configured.
+   */
+  public final static class Builder {
+    private final SchemaProvider schemas;
+    private ObservableWaveletData.Factory<?> holderFactory;
+    private SilentOperationSink<? super WaveletOperation> sink;
+    private ParticipantId author;
+
+    public Builder(SchemaProvider schemas) {
+      this.schemas = schemas;
+    }
+
+    public Builder with(SilentOperationSink<? super WaveletOperation> sink) {
+      this.sink = sink;
+      return this;
+    }
+
+    public Builder with(ObservableWaveletData.Factory<?> holderFactory) {
+      this.holderFactory = holderFactory;
+      return this;
+    }
+
+    public Builder with(ParticipantId author) {
+      this.author = author;
+      return this;
+    }
+
+    public OpBasedWaveletFactory build() {
+      if (holderFactory == null) {
+        DocumentFactory<?> docFactory = 
ObservablePluggableMutableDocument.createFactory(schemas);
+        holderFactory = WaveletDataImpl.Factory.create(docFactory);
+      }
+      if (sink == null) {
+        sink = SilentOperationSink.VOID;
+      }
+      if (author == null) {
+        // Old tests expect this.
+        author = FAKE_PARTICIPANT;
+      }
+      return new OpBasedWaveletFactory(holderFactory, sink, author);
+    }
+  }
+
+  private static final ParticipantId FAKE_PARTICIPANT = new 
ParticipantId("f...@example.com");
+
+  // Parameters with which to create the OpBasedWavelets.
+  private final ObservableWaveletData.Factory<?> holderFactory;
+  private final SilentOperationSink<? super WaveletOperation> sink;
+  private final ParticipantId author;
+
+  // Testing hacks.
+  private MockWaveletOperationContextFactory lastContextFactory;
+  private MockParticipationHelper lastAuthoriser;
+
+  /**
+   * Creates a factory, which creates op-based waves that adapt wave data
+   * holders provided by another factory, sending produced operations to a 
given
+   * sink.
+   *
+   * @param holderFactory factory for providing wave data holders
+   * @param sink sink to which produced operations are sent
+   * @param author id to which edits are to be attributed
+   */
+  private OpBasedWaveletFactory(ObservableWaveletData.Factory<?> holderFactory,
+      SilentOperationSink<? super WaveletOperation> sink,
+      ParticipantId author) {
+    this.holderFactory = holderFactory;
+    this.sink = sink;
+    this.author = author;
+  }
+
+  public static Builder builder(SchemaProvider schemas) {
+    return new Builder(schemas);
+  }
+
+  @Override
+  public OpBasedWavelet create() {
+    IdGenerator gen = FakeIdGenerator.create();
+    return create(gen.newWaveId(), gen.newConversationWaveletId(), 
FAKE_PARTICIPANT);
+  }
+
+  @Override
+  public OpBasedWavelet create(WaveId waveId, WaveletId waveletId, 
ParticipantId creator) {
+    long now = System.currentTimeMillis();
+    HashedVersion v0 = HashedVersion.unsigned(0);
+    ObservableWaveletData waveData = holderFactory
+        .create(new EmptyWaveletSnapshot(waveId, waveletId, creator, v0, now));
+    lastContextFactory = new 
MockWaveletOperationContextFactory().setParticipantId(author);
+    lastAuthoriser = new MockParticipationHelper();
+    SilentOperationSink<WaveletOperation> executor =
+        Executor.<WaveletOperation, WaveletData>build(waveData);
+    SilentOperationSink<WaveletOperation> out = new 
VersionIncrementingSink(waveData, sink);
+    return new OpBasedWavelet(waveId, waveData, lastContextFactory, 
lastAuthoriser, executor, out);
+  }
+
+  /**
+   * Gets the authoriser provided to help the last {@link OpBasedWavelet} that
+   * was created. The result is undefined if no wavelets have been created.
+   */
+  public MockParticipationHelper getLastAuthoriser() {
+    return lastAuthoriser;
+  }
+
+  /**
+   * Gets the helper provided to the last {@link OpBasedWavelet} that was
+   * created. The result is undefined if no wavelets have been created.
+   */
+  public MockWaveletOperationContextFactory getLastContextFactory() {
+    return lastContextFactory;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/OpMatchers.java
----------------------------------------------------------------------
diff --git 
a/wave/src/test/java/org/waveprotocol/wave/model/testing/OpMatchers.java 
b/wave/src/test/java/org/waveprotocol/wave/model/testing/OpMatchers.java
new file mode 100644
index 0000000..8c17f5c
--- /dev/null
+++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/OpMatchers.java
@@ -0,0 +1,94 @@
+/**
+ * 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.testing;
+
+import org.waveprotocol.wave.model.operation.wave.AddParticipant;
+import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation;
+import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Hamcrest matchers for CWM operations. Many of these are for use in JMock
+ * tests as replacements for the deprecated and broken
+ * {@link org.jmock.Expectations#a(Class)} and non-typesafe alternative
+ * {@link org.hamcrest.Matchers#instanceOf(Class)}.
+ *
+ */
+public class OpMatchers {
+  /**
+   * Alternative to Matchers.a(AddParticipant.class) since JMock/Hamcrest's
+   * implementation is deprecated due to being broken in Java 5 and 6.
+   */
+  public static Matcher<WaveletOperation> addParticipantOperation() {
+    return new BaseMatcher<WaveletOperation>() {
+      @Override
+      public boolean matches(Object obj) {
+        return obj instanceof AddParticipant;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(" instanceof AddParticipant");
+      }
+    };
+  }
+
+  /** Creates a matcher for operations created by the given author. */
+  public static Matcher<WaveletOperation> opBy(final String author) {
+    return new TypeSafeMatcher<WaveletOperation>() {
+      @Override
+      public boolean matchesSafely(WaveletOperation op) {
+        return author.equals(op.getContext().getCreator().getAddress());
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(" op created by " + author);
+      }
+    };
+  }
+
+  /**
+   * Alternative to Matchers.a(WaveletBlipOperation.class) since
+   * JMock/Hamcrest's implementation is deprecated due to being broken in Java 
5
+   * and 6.
+   */
+  public static Matcher<WaveletOperation> waveletBlipOperation() {
+    return new BaseMatcher<WaveletOperation>() {
+      @Override
+      public boolean matches(Object obj) {
+        return obj instanceof WaveletBlipOperation;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(" instanceof WaveletBlipOperation");
+      }
+    };
+  }
+
+  /** Uninstantiable. */
+  private OpMatchers() {
+  }
+}

Reply via email to