http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/src/org/waveprotocol/wave/model/testing/WaveletDataFactory.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/wave/model/testing/WaveletDataFactory.java b/src/org/waveprotocol/wave/model/testing/WaveletDataFactory.java deleted file mode 100644 index bf8ff93..0000000 --- a/src/org/waveprotocol/wave/model/testing/WaveletDataFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * 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.version.HashedVersion; -import org.waveprotocol.wave.model.wave.ParticipantId; -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; - -/** - * Exposes any {@link ObservableWaveletData.Factory} as a {@link Factory}, by - * injecting suitable dependencies for testing. - * - */ -public final class WaveletDataFactory<T extends WaveletData> implements Factory<T> { - private final static WaveId WAVE_ID; - private final static WaveletId WAVELET_ID; - private static final ParticipantId PARTICIPANT_ID = new ParticipantId("[email protected]"); - - static { - IdGenerator gen = FakeIdGenerator.create(); - WAVE_ID = gen.newWaveId(); - WAVELET_ID = gen.newConversationWaveletId(); - } - - private final WaveletData.Factory<T> factory; - - private WaveletDataFactory(WaveletData.Factory<T> factory) { - this.factory = factory; - } - - public static <T extends WaveletData> Factory<T> of(WaveletData.Factory<T> factory) { - return new WaveletDataFactory<T>(factory); - } - - @Override - public T create() { - return factory.create(new EmptyWaveletSnapshot(WAVE_ID, WAVELET_ID, PARTICIPANT_ID, - HashedVersion.unsigned(0), 0)); - } -}
http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/robots/operations/BlipOperationServicesTest.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/robots/operations/BlipOperationServicesTest.java b/test/org/waveprotocol/box/server/robots/operations/BlipOperationServicesTest.java index 591fa83..dfddfb7 100644 --- a/test/org/waveprotocol/box/server/robots/operations/BlipOperationServicesTest.java +++ b/test/org/waveprotocol/box/server/robots/operations/BlipOperationServicesTest.java @@ -33,7 +33,7 @@ import com.google.wave.api.data.ApiView; import org.waveprotocol.box.server.robots.OperationContext; import org.waveprotocol.box.server.robots.OperationContextImpl; import org.waveprotocol.box.server.robots.RobotsTestBase; -import org.waveprotocol.box.server.robots.testing.OperationServiceHelper; +import org.waveprotocol.box.server.robots.operations.testing.OperationServiceHelper; import org.waveprotocol.box.server.robots.util.ConversationUtil; import org.waveprotocol.wave.model.conversation.ConversationBlip; import org.waveprotocol.wave.model.conversation.ObservableConversation; http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/robots/operations/DocumentModifyServiceTest.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/robots/operations/DocumentModifyServiceTest.java b/test/org/waveprotocol/box/server/robots/operations/DocumentModifyServiceTest.java index 3ba955b..68651c1 100644 --- a/test/org/waveprotocol/box/server/robots/operations/DocumentModifyServiceTest.java +++ b/test/org/waveprotocol/box/server/robots/operations/DocumentModifyServiceTest.java @@ -39,7 +39,7 @@ import com.google.wave.api.impl.DocumentModifyAction.ModifyHow; import com.google.wave.api.impl.DocumentModifyQuery; import org.waveprotocol.box.server.robots.RobotsTestBase; -import org.waveprotocol.box.server.robots.testing.OperationServiceHelper; +import org.waveprotocol.box.server.robots.operations.testing.OperationServiceHelper; import org.waveprotocol.wave.model.conversation.ObservableConversationBlip; import org.waveprotocol.wave.model.document.Document; import org.waveprotocol.wave.model.document.util.LineContainers; http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/robots/operations/FetchWaveServiceTest.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/robots/operations/FetchWaveServiceTest.java b/test/org/waveprotocol/box/server/robots/operations/FetchWaveServiceTest.java index 7779bcc..fe6a587 100644 --- a/test/org/waveprotocol/box/server/robots/operations/FetchWaveServiceTest.java +++ b/test/org/waveprotocol/box/server/robots/operations/FetchWaveServiceTest.java @@ -30,7 +30,7 @@ import com.google.wave.api.OperationType; import org.waveprotocol.box.server.robots.OperationContextImpl; import org.waveprotocol.box.server.robots.RobotsTestBase; -import org.waveprotocol.box.server.robots.testing.OperationServiceHelper; +import org.waveprotocol.box.server.robots.operations.testing.OperationServiceHelper; import org.waveprotocol.box.server.waveserver.WaveletProvider; import org.waveprotocol.wave.model.conversation.ObservableConversation; http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/robots/operations/ParticipantServicesTest.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/robots/operations/ParticipantServicesTest.java b/test/org/waveprotocol/box/server/robots/operations/ParticipantServicesTest.java index 582ae54..2595808 100644 --- a/test/org/waveprotocol/box/server/robots/operations/ParticipantServicesTest.java +++ b/test/org/waveprotocol/box/server/robots/operations/ParticipantServicesTest.java @@ -30,7 +30,7 @@ import com.google.wave.api.OperationRequest.Parameter; import org.waveprotocol.box.server.robots.RobotsTestBase; import org.waveprotocol.box.server.robots.OperationContext; import org.waveprotocol.box.server.robots.OperationContextImpl; -import org.waveprotocol.box.server.robots.testing.OperationServiceHelper; +import org.waveprotocol.box.server.robots.operations.testing.OperationServiceHelper; import org.waveprotocol.box.server.robots.util.ConversationUtil; import org.waveprotocol.wave.model.conversation.ObservableConversation; import org.waveprotocol.wave.model.wave.ParticipantId; http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/robots/operations/WaveletSetTitleServiceTest.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/robots/operations/WaveletSetTitleServiceTest.java b/test/org/waveprotocol/box/server/robots/operations/WaveletSetTitleServiceTest.java index d995638..bdc12b0 100644 --- a/test/org/waveprotocol/box/server/robots/operations/WaveletSetTitleServiceTest.java +++ b/test/org/waveprotocol/box/server/robots/operations/WaveletSetTitleServiceTest.java @@ -26,7 +26,7 @@ import com.google.wave.api.OperationRequest.Parameter; import com.google.wave.api.OperationType; import org.waveprotocol.box.server.robots.RobotsTestBase; -import org.waveprotocol.box.server.robots.testing.OperationServiceHelper; +import org.waveprotocol.box.server.robots.operations.testing.OperationServiceHelper; import org.waveprotocol.wave.model.conversation.ObservableConversationBlip; import org.waveprotocol.wave.model.conversation.TitleHelper; import org.waveprotocol.wave.model.document.util.LineContainers; http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/robots/operations/testing/OperationServiceHelper.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/robots/operations/testing/OperationServiceHelper.java b/test/org/waveprotocol/box/server/robots/operations/testing/OperationServiceHelper.java new file mode 100644 index 0000000..a5708a4 --- /dev/null +++ b/test/org/waveprotocol/box/server/robots/operations/testing/OperationServiceHelper.java @@ -0,0 +1,120 @@ +/** + * 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.box.server.robots.operations.testing; + +import static org.mockito.Mockito.mock; + +import com.google.wave.api.data.converter.EventDataConverter; +import com.google.wave.api.data.converter.v22.EventDataConverterV22; + +import org.waveprotocol.box.server.robots.OperationContextImpl; +import org.waveprotocol.box.server.robots.RobotWaveletData; +import org.waveprotocol.box.server.robots.operations.OperationService; +import org.waveprotocol.box.server.robots.util.ConversationUtil; +import org.waveprotocol.box.server.waveserver.WaveletProvider; +import org.waveprotocol.wave.model.conversation.ObservableConversation; +import org.waveprotocol.wave.model.conversation.WaveletBasedConversation; +import org.waveprotocol.wave.model.id.IdURIEncoderDecoder; +import org.waveprotocol.wave.model.id.WaveletName; +import org.waveprotocol.wave.model.operation.SilentOperationSink; +import org.waveprotocol.wave.model.operation.wave.BasicWaveletOperationContextFactory; +import org.waveprotocol.wave.model.operation.wave.WaveletOperation; +import org.waveprotocol.wave.model.testing.BasicFactories; +import org.waveprotocol.wave.model.testing.FakeIdGenerator; +import org.waveprotocol.wave.model.version.HashedVersionFactory; +import org.waveprotocol.wave.model.version.HashedVersionFactoryImpl; +import org.waveprotocol.wave.model.wave.ParticipantId; +import org.waveprotocol.wave.model.wave.ParticipationHelper; +import org.waveprotocol.wave.model.wave.data.DocumentFactory; +import org.waveprotocol.wave.model.wave.data.DocumentOperationSink; +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.WaveletDataImpl; +import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet; +import org.waveprotocol.wave.util.escapers.jvm.JavaUrlCodec; + +/** + * Helper for testing {@link OperationService}. Puts a single empty + * conversational wavelet with one participant in the operation context. + * + * @author [email protected] (Lennard de Rijk) + */ +public class OperationServiceHelper { + + private static final IdURIEncoderDecoder URI_CODEC = + new IdURIEncoderDecoder(new JavaUrlCodec()); + private static final HashedVersionFactory HASH_FACTORY = new HashedVersionFactoryImpl(URI_CODEC); + private static final DocumentFactory<? extends DocumentOperationSink> DOCUMENT_FACTORY = + BasicFactories.observablePluggableMutableDocumentFactory(); + + private final WaveletProvider waveletProvider; + private final OperationContextImpl context; + + /** + * Constructs a new {@link OperationServiceHelper} with a wavelet with the + * name and participant that are passed in. + * + * @param waveletName the name of the empty wavelet to open in the context. + * @param participant the participant that should be on that empty wavelet. + */ + public OperationServiceHelper(WaveletName waveletName, ParticipantId participant) { + waveletProvider = mock(WaveletProvider.class); + EventDataConverter converter = new EventDataConverterV22(); + + ObservableWaveletData waveletData = WaveletDataImpl.Factory.create(DOCUMENT_FACTORY).create( + new EmptyWaveletSnapshot(waveletName.waveId, waveletName.waveletId, participant, + HASH_FACTORY.createVersionZero(waveletName), 0L)); + waveletData.addParticipant(participant); + + BasicWaveletOperationContextFactory CONTEXT_FACTORY = + new BasicWaveletOperationContextFactory(participant); + + SilentOperationSink<WaveletOperation> executor = + SilentOperationSink.Executor.<WaveletOperation, WaveletData>build(waveletData); + OpBasedWavelet wavelet = + new OpBasedWavelet(waveletData.getWaveId(), waveletData, CONTEXT_FACTORY, + ParticipationHelper.DEFAULT, executor, SilentOperationSink.VOID); + + // Make a conversation with an empty root blip + WaveletBasedConversation.makeWaveletConversational(wavelet); + ConversationUtil conversationUtil = new ConversationUtil(FakeIdGenerator.create()); + ObservableConversation conversation = conversationUtil.buildConversation(wavelet).getRoot(); + conversation.getRootThread().appendBlip(); + + context = new OperationContextImpl(waveletProvider, converter, conversationUtil); + context.putWavelet(waveletName.waveId, waveletName.waveletId, + new RobotWaveletData(waveletData, HASH_FACTORY.createVersionZero(waveletName))); + } + + /** + * @return the {@link WaveletProvider} mock + */ + public WaveletProvider getWaveletProvider() { + return waveletProvider; + } + + /** + * @return the {@link OperationContextImpl} with the empty wavelet opened. + */ + public OperationContextImpl getContext() { + return context; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/rpc/testing/FakeServerRpcController.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/rpc/testing/FakeServerRpcController.java b/test/org/waveprotocol/box/server/rpc/testing/FakeServerRpcController.java new file mode 100644 index 0000000..fa45cd6 --- /dev/null +++ b/test/org/waveprotocol/box/server/rpc/testing/FakeServerRpcController.java @@ -0,0 +1,84 @@ +/** + * 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.box.server.rpc.testing; + +import static org.waveprotocol.box.server.util.testing.TestingConstants.USER; + +import com.google.protobuf.RpcCallback; + +import org.waveprotocol.box.server.rpc.ServerRpcController; +import org.waveprotocol.wave.model.wave.ParticipantId; + + +/** + * An {@code RpcController} that just handles error text and failure condition. + */ +public class FakeServerRpcController implements ServerRpcController { + private boolean failed = false; + private String errorText = null; + + @Override + public String errorText() { + return errorText; + } + + @Override + public boolean failed() { + return failed; + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public void notifyOnCancel(RpcCallback<Object> arg) { + } + + @Override + public void reset() { + failed = false; + errorText = null; + } + + @Override + public void setFailed(String error) { + failed = true; + errorText = error; + } + + @Override + public void startCancel() { + } + + @Override + public ParticipantId getLoggedInUser() { + return ParticipantId.ofUnsafe(USER); + } + + @Override + public void cancel() { + } + + @Override + public void run() { + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/util/testing/ExceptionLogHandler.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/util/testing/ExceptionLogHandler.java b/test/org/waveprotocol/box/server/util/testing/ExceptionLogHandler.java new file mode 100644 index 0000000..7ea3550 --- /dev/null +++ b/test/org/waveprotocol/box/server/util/testing/ExceptionLogHandler.java @@ -0,0 +1,65 @@ +/** + * 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.box.server.util.testing; + +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * A log handler which throws a runtime exception at and above a set log level. + * Useful for catching erroneous conditions during testing, which are only + * reported to the logs. + * + * @author [email protected] (Michael Kuntzman) + */ +public class ExceptionLogHandler extends Handler { + /** The integer value of the minimum fatal log level. */ + private final int fatalLevel; + + /** + * Constructs an ExceptionLogHandler that throws a runtime exception at and above the specified + * log level. + * + * @param fatalLevel the minimum log level for which to throw an exception. + */ + public ExceptionLogHandler(Level fatalLevel) { + this.fatalLevel = fatalLevel.intValue(); + } + + /** + * @throws RuntimeException if the log record is at or above the fatal log level. + */ + @Override + public void publish(LogRecord record) { + if (record.getLevel().intValue() >= fatalLevel) { + throw new RuntimeException(record.getLevel() + ": " + record.getMessage(), + record.getThrown()); + } + } + + @Override + public void flush() { + } + + @Override + public void close() { + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/util/testing/Matchers.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/util/testing/Matchers.java b/test/org/waveprotocol/box/server/util/testing/Matchers.java new file mode 100644 index 0000000..72be27a --- /dev/null +++ b/test/org/waveprotocol/box/server/util/testing/Matchers.java @@ -0,0 +1,172 @@ +/** + * 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.box.server.util.testing; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +import java.util.Collection; + +/** + * Additional matchers to go with JUnit4's assertThat and assumeThat. + * + * @author [email protected] (Michael Kuntzman) + */ +// TODO(Michael): Maybe move this class to the libraries repository/branch. +public class Matchers { + /** + * Nicer aliases for some of the methods in this class, which may conflict with methods in other + * packages (potential conficts noted for each alias). + */ + public static class Aliases { + /** + * Alias for "containsString". May conflict with "org.mockito.Mockito.contains". + * + * @param substring to look for. + * @return a matcher for checking that a string contains the specified substring. + */ + public static TypeSafeMatcher<String> contains(final String substring) { + return containsString(substring); + } + + /** + * Alias for "matchesRegex". May conflict with "org.mockito.Mockito.matches". + * + * @param regularExpression to match against. + * @return a matcher for checking that a string matches the specified regular expression. + */ + public static TypeSafeMatcher<String> matches(final String regularExpression) { + return matchesRegex(regularExpression); + } + } + + /** + * A more user-friendly version of org.junit.matchers.JUnitMatchers.hasItem(T element). Allows a + * more verbose failure than assertTrue(collection.contains(item)). The matcher produces + * "Expected: a collection containing '...' got: '...'", whereas assertTrue produces merely + * "AssertionFailedError". + * Usage: static import, then assertThat(collection, contains(item)). + * + * @param item to look for. + * @return a matcher for checking that a collection contains the specified item. + */ + public static <T> TypeSafeMatcher<Collection<? super T>> contains(final T item) { + return new TypeSafeMatcher<Collection<? super T>>() { + @Override + public boolean matchesSafely(Collection<? super T> collection) { + return collection.contains(item); + } + + @Override + public void describeTo(Description description) { + description.appendText("a collection containing ").appendValue(item); + } + }; + } + + /** + * Same as JUnitMatchers.containsString. Allows a more verbose failure than + * assertTrue(str.contains(substring)). + * Usage: static import, then assertThat(str, containsString(substring)). + * + * @param substring to look for. + * @return a matcher for checking that a string contains the specified substring. + */ + public static TypeSafeMatcher<String> containsString(final String substring) { + return new TypeSafeMatcher<String>() { + @Override + public boolean matchesSafely(String str) { + return str.contains(substring); + } + + @Override + public void describeTo(Description description) { + description.appendText("a string containing ").appendValue(substring); + } + }; + } + + /** + * The negative version of "contains" for a collection. Allows a more verbose failure than + * assertFalse(collection.contains(item)). + * Usage: static import, then assertThat(collection, doesNotContain(item)). + * + * @param item to look for. + * @return a matcher for checking that a collection does not contain the specified item. + */ + public static <T> TypeSafeMatcher<Collection<? super T>> doesNotContain(final T item) { + return new TypeSafeMatcher<Collection<? super T>>() { + @Override + public boolean matchesSafely(Collection<? super T> collection) { + return !collection.contains(item); + } + + @Override + public void describeTo(Description description) { + description.appendText("a collection NOT containing ").appendValue(item); + } + }; + } + + /** + * The negative version of "contains" for a string (or "containsString"). Allows a more verbose + * failure than assertFalse(str.contains(substring)). + * Usage: static import, then assertThat(str, doesNotContain(substring)). + * + * @param substring to look for. + * @return a matcher for checking that a string contains the specified substring. + */ + public static TypeSafeMatcher<String> doesNotContain(final String substring) { + return new TypeSafeMatcher<String>() { + @Override + public boolean matchesSafely(String str) { + return !str.contains(substring); + } + + @Override + public void describeTo(Description description) { + description.appendText("a string NOT containing ").appendValue(substring); + } + }; + } + + /** + * Allows a more verbose failure than assertTrue(str.matches(regex)). The matcher produces + * "Expected: a string matching regex '...' got: '...'", whereas assertTrue produces merely + * "AssertionFailedError". + * Usage: static import, then assertThat(str, matchesRegex(regex)). + * + * @param regularExpression to match against. + * @return a matcher for checking that a string matches the specified regular expression. + */ + public static TypeSafeMatcher<String> matchesRegex(final String regularExpression) { + return new TypeSafeMatcher<String>() { + @Override + public boolean matchesSafely(String str) { + return str.matches(regularExpression); + } + + @Override + public void describeTo(Description description) { + description.appendText("a string matching regex ").appendValue(regularExpression); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/box/server/util/testing/TestingConstants.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/util/testing/TestingConstants.java b/test/org/waveprotocol/box/server/util/testing/TestingConstants.java new file mode 100644 index 0000000..ed0d95f --- /dev/null +++ b/test/org/waveprotocol/box/server/util/testing/TestingConstants.java @@ -0,0 +1,66 @@ +/** + * 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.box.server.util.testing; + +import org.waveprotocol.wave.model.id.WaveId; +import org.waveprotocol.wave.model.id.WaveletId; +import org.waveprotocol.wave.model.id.WaveletName; +import org.waveprotocol.wave.model.wave.ParticipantId; + +/** + * Commonly used constants for unit testing. Some constants taken from + * previously existing test cases. + * + * @author [email protected] (Michael Kuntzman) + */ +// TODO(Michael): Maybe move this class to the libraries repository/branch. +public interface TestingConstants { + public static final String BLIP_ID = "b+blip"; + + public static final String MESSAGE = "The quick brown fox jumps over the lazy dog"; + + public static final String MESSAGE2 = "Why's the rum gone?"; + + public static final String MESSAGE3 = "There is no spoon"; + + public static final String DOMAIN = "host.com"; + + public static final String OTHER_USER_NAME = "other"; + + public static final String OTHER_USER = OTHER_USER_NAME + "@" + DOMAIN; + + public static final ParticipantId OTHER_PARTICIPANT = new ParticipantId(OTHER_USER); + + public static final int PORT = 9876; + + public static final String USER_NAME = "user"; + + public static final String USER = USER_NAME + "@" + DOMAIN; + + public static final char[] PASSWORD = "password".toCharArray(); + + public static final ParticipantId PARTICIPANT = new ParticipantId(USER); + + public static final WaveId WAVE_ID = WaveId.of(DOMAIN, "w+wave"); + + public static final WaveletId WAVELET_ID = WaveletId.of(DOMAIN, "wavelet"); + + public static final WaveletName WAVELET_NAME = WaveletName.of(WAVE_ID, WAVELET_ID); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/ContentSerialisationUtil.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/ContentSerialisationUtil.java b/test/org/waveprotocol/wave/client/editor/testing/ContentSerialisationUtil.java new file mode 100644 index 0000000..81d88ce --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/ContentSerialisationUtil.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.client.editor.testing; + +import org.waveprotocol.wave.client.editor.Editor; +import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas; + +import org.waveprotocol.wave.model.document.util.DocProviders; +import org.waveprotocol.wave.model.document.util.XmlStringBuilder; + +/** + * Utility that decorates editors by adding the ability to get/set content using types + * other than DocInitialisation ops. + * + * TODO(patcoleman): figure out which class(/es) should be natively supported inside Editor + * the interface, and which should be decorated within this class. + * + * @author [email protected] (Pat Coleman) + */ +public class ContentSerialisationUtil { + /** Static utility, hence private constructor. */ + private ContentSerialisationUtil() {} + + /// String support + + /** Gets the editor's persistent document as a String. */ + public static String getContentString(Editor editor) { + return XmlStringBuilder.innerXml(editor.getPersistentDocument()).toString(); + } + + /** + * Sets the editor's content to a given string - the accepted format currently is XML, + * note to be careful that attribute values are surrounded by ", not '. + */ + public static void setContentString(Editor editor, String content) { + editor.setContent(DocProviders.POJO.parse(content).asOperation(), + ConversationSchemas.BLIP_SCHEMA_CONSTRAINTS); + } + + /// Other classes to come... +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/DocumentFreeSelectionHelper.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/DocumentFreeSelectionHelper.java b/test/org/waveprotocol/wave/client/editor/testing/DocumentFreeSelectionHelper.java new file mode 100644 index 0000000..c540ca0 --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/DocumentFreeSelectionHelper.java @@ -0,0 +1,106 @@ +/** + * 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.client.editor.testing; + +import org.waveprotocol.wave.client.editor.content.ContentNode; +import org.waveprotocol.wave.client.editor.content.ContentRange; +import org.waveprotocol.wave.client.editor.content.FocusedContentRange; +import org.waveprotocol.wave.client.editor.selection.content.SelectionHelper; +import org.waveprotocol.wave.model.document.util.FocusedRange; +import org.waveprotocol.wave.model.document.util.Point; +import org.waveprotocol.wave.model.document.util.Range; + +/** + * A simple selection helper implementation with no knowledge of a content tree + * + * @author [email protected] (Pat Coleman) + */ +public class DocumentFreeSelectionHelper implements SelectionHelper { + /** Track the simple range directly. */ + FocusedRange selection = null; + + public DocumentFreeSelectionHelper(int start, int end) { + selection = new FocusedRange(start, end); + } + + public DocumentFreeSelectionHelper(FocusedRange range) { + selection = range; + } + + @Override + public void clearSelection() { + selection = null; + } + + @Override + public FocusedRange getSelectionRange() { + return selection; + } + + @Override + public Range getOrderedSelectionRange() { + return selection != null ? selection.asRange() : null; + } + + @Override + public void setSelectionRange(FocusedRange selection) { + this.selection = selection; + } + + @Override + public Point<ContentNode> getFirstValidSelectionPoint() { + throw new UnsupportedOperationException("DocumentFree SelectionHelper has no document content"); + } + + @Override + public Point<ContentNode> getLastValidSelectionPoint() { + throw new UnsupportedOperationException("DocumentFree SelectionHelper has no document content"); + } + + @Override + public FocusedContentRange getSelectionPoints() { + throw new UnsupportedOperationException("DocumentFree SelectionHelper has no document content"); + } + + @Override + public ContentRange getOrderedSelectionPoints() { + throw new UnsupportedOperationException("DocumentFree SelectionHelper has no document content"); + } + + @Override + public boolean isValidSelectionPoint(Point<ContentNode> cp) { + throw new UnsupportedOperationException("DocumentFree SelectionHelper has no document content"); + } + + @Override + public void setCaret(Point<ContentNode> caret) { + throw new UnsupportedOperationException("DocumentFree SelectionHelper has no document content"); + } + + @Override + public void setSelectionPoints(Point<ContentNode> start, Point<ContentNode> end) { + throw new UnsupportedOperationException("DocumentFree SelectionHelper has no document content"); + } + + @Override + public void setCaret(int caret) { + throw new UnsupportedOperationException("DocumentFree SelectionHelper has no document content"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/FakeEditorContext.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/FakeEditorContext.java b/test/org/waveprotocol/wave/client/editor/testing/FakeEditorContext.java new file mode 100644 index 0000000..d1dc06c --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/FakeEditorContext.java @@ -0,0 +1,105 @@ +/** + * 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.client.editor.testing; + +import org.waveprotocol.wave.client.editor.EditorContext; +import org.waveprotocol.wave.client.editor.EditorUpdateEvent.EditorUpdateListener; +import org.waveprotocol.wave.client.editor.Responsibility; +import org.waveprotocol.wave.client.editor.ResponsibilityManagerImpl; +import org.waveprotocol.wave.client.editor.content.CMutableDocument; +import org.waveprotocol.wave.client.editor.content.misc.CaretAnnotations; +import org.waveprotocol.wave.client.editor.selection.content.SelectionHelper; + +/** + * Representing a (possibly-POJO) implementation of the editor context interface, + * by using members supplied on construction. + * + * @author [email protected] (Pat Coleman) + */ +public class FakeEditorContext implements EditorContext { + private final CMutableDocument document; + private final CaretAnnotations caretAnnotations; + private final String imeCompositionState; + private final SelectionHelper selectionHelper; + private final Responsibility.Manager responsibility = new ResponsibilityManagerImpl(); + + public FakeEditorContext(CMutableDocument doc, CaretAnnotations caret, String imeState, + SelectionHelper selection) { + this.document = doc; + this.caretAnnotations = caret; + this.imeCompositionState = imeState; + this.selectionHelper = selection; + } + + @Override + public void blur() { + // NO-OP + } + + @Override + public void focus(boolean collapsed) { + // NO-OP + } + + @Override + public CaretAnnotations getCaretAnnotations() { + return caretAnnotations; + } + + @Override + public CMutableDocument getDocument() { + return document; + } + + @Override + public String getImeCompositionState() { + return imeCompositionState; + } + + @Override + public SelectionHelper getSelectionHelper() { + return selectionHelper; + } + + @Override + public boolean isEditing() { + return false; + } + + @Override + public Responsibility.Manager getResponsibilityManager() { + return responsibility; + } + + @Override + public void addUpdateListener(EditorUpdateListener listener) { + throw new AssertionError("Not implemented"); + } + + @Override + public void removeUpdateListener(EditorUpdateListener listener) { + throw new AssertionError("Not implemented"); + } + + @Override + public void undoableSequence(Runnable cmd) { + cmd.run(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/FakeEditorEvent.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/FakeEditorEvent.java b/test/org/waveprotocol/wave/client/editor/testing/FakeEditorEvent.java new file mode 100755 index 0000000..d31cbe8 --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/FakeEditorEvent.java @@ -0,0 +1,104 @@ +/** + * 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.client.editor.testing; + +import org.waveprotocol.wave.client.common.util.FakeSignalEvent; +import org.waveprotocol.wave.client.editor.constants.BrowserEvents; +import org.waveprotocol.wave.client.editor.content.ContentPoint; +import org.waveprotocol.wave.client.editor.event.EditorEvent; + +/** + * Use this class to mock events for EditorImpl methods + * + */ +public class FakeEditorEvent extends FakeSignalEvent implements EditorEvent { + + public static SignalEventFactory<FakeEditorEvent> ED_FACTORY = + new SignalEventFactory<FakeEditorEvent>() { + @Override public FakeEditorEvent create() { + return new FakeEditorEvent(); + } + }; + + /** + * @param type + * @return a fake event of the given type + */ + public static FakeEditorEvent create(String type) { + return FakeSignalEvent.createEvent(ED_FACTORY, type); + } + + + /** + * Construct from a KeySignalType and a key code + */ + public static FakeEditorEvent create(KeySignalType type, int keyCode) { + return FakeSignalEvent.createKeyPress(ED_FACTORY, type, keyCode, null); + } + + /** + * @return A fake paste event + */ + public static FakeEditorEvent createPasteEvent() { + return create("paste"); + } + + /** + * Creates a composition start, some composition updates, and a composition end + * + * @param numUpdates + * @return the events in order + */ + public static FakeEditorEvent[] compositionSequence(int numUpdates) { + FakeEditorEvent[] evts = new FakeEditorEvent[numUpdates + 2]; + + evts[0] = create(BrowserEvents.COMPOSITIONSTART); + for (int i = 1; i <= numUpdates; i++) { + evts[i] = create(BrowserEvents.COMPOSITIONUPDATE); + } + evts[numUpdates + 1] = create(BrowserEvents.COMPOSITIONEND); + return evts; + } + + private boolean shouldAllowDefault = false; + private ContentPoint caret; + + @Override + public void allowBrowserDefault() { + shouldAllowDefault = true; + } + + + @Override + public ContentPoint getCaret() { + return caret; + } + + @Override + public void setCaret(ContentPoint caret) { + this.caret = caret; + } + + @Override + public boolean shouldAllowBrowserDefault() { + return shouldAllowDefault; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/FakeUser.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/FakeUser.java b/test/org/waveprotocol/wave/client/editor/testing/FakeUser.java new file mode 100644 index 0000000..e24762d --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/FakeUser.java @@ -0,0 +1,198 @@ +/** + * 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.client.editor.testing; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.Text; +import org.waveprotocol.wave.client.common.util.DomHelper; +import org.waveprotocol.wave.client.editor.extract.InconsistencyException.HtmlInserted; +import org.waveprotocol.wave.client.editor.extract.InconsistencyException.HtmlMissing; +import org.waveprotocol.wave.client.editor.extract.TypingExtractor; +import org.waveprotocol.wave.client.editor.extract.TypingExtractor.SelectionSource; +import org.waveprotocol.wave.client.editor.impl.HtmlView; + +import org.waveprotocol.wave.model.document.util.Point; + +/** + * Simulates the behaviour of a browser updating the DOM due to a user typing + * + * @author [email protected] (Daniel Danilatos) + */ +public class FakeUser implements SelectionSource{ + + public enum Action { + MOVE, + TYPE, + DELETE, + BACKSPACE, + SPLIT + } + + private final HtmlView htmlView; + + private Point<Node> caret; + + public FakeUser(HtmlView htmlView) { + this.htmlView = htmlView; + } + + @SuppressWarnings("unchecked") // NOTE(zdwang): This is for (Point<Node>) action[1] + public void run(TypingExtractor extractor, Object... actions) throws HtmlMissing, HtmlInserted { + for (Object a : actions) { + Object[] action = (Object[]) a; + Point<Node> sel = getSelectionStart(); + Text txt = sel == null ? null : sel.getContainer().<Text>cast(); + Action type = (Action) action[0]; + switch (type) { + case MOVE: + //extractor.flush(); + setCaret((Point<Node>) action[1]); + break; + case TYPE: + String typed = (String) action[1]; + extractor.somethingHappened(getSelectionStart()); + if (sel.isInTextNode()) { + txt.insertData(sel.getTextOffset(), (String) action[1]); + moveCaret(((String) action[1]).length()); + } else { + txt = Document.get().createTextNode(typed); + sel.getContainer().insertBefore(txt, sel.getNodeAfter()); + setCaret(Point.inText((Node)txt, typed.length())); + } + break; + case BACKSPACE: + case DELETE: + extractor.somethingHappened(getSelectionStart()); + int amount = (Integer) action[1]; + if (type == Action.BACKSPACE) { + moveCaret(-amount); + } + deleteText(amount); + break; + case SPLIT: + sel.getContainer().<Text>cast().splitText(sel.getTextOffset()); + moveCaret(0); + break; + } + } + } + + private void deleteText(int amount) { + Point<Node> sel = getSelectionStart(); + Text txt = sel == null ? null : sel.getContainer().<Text>cast(); + int startIndex = sel.getTextOffset(), len; + while (amount > 0) { + if (txt == null || !DomHelper.isTextNode(txt)) { + throw new RuntimeException("Action ran off end of text node"); + } + String data = txt.getData(); + int remainingInNode = data.length() - startIndex; + if (remainingInNode >= amount) { + len = amount; + } else { + len = remainingInNode; + } + txt.setData(data.substring(0, startIndex) + data.substring(startIndex + len)); + amount -= len; + startIndex = 0; + txt = htmlView.getNextSibling(txt).cast(); + } + moveCaret(0); + } + + public void moveCaret(int distance) { + Point<Node> caret = getSelectionStart(); + if (!caret.isInTextNode()) { + Node before = Point.nodeBefore(htmlView, caret.asElementPoint()); + if (DomHelper.isTextNode(before)) { + caret = Point.inText(before, before.<Text>cast().getLength()); + } else if (DomHelper.isTextNode(caret.getNodeAfter())) { + caret = Point.inText(caret.getNodeAfter(), 0); + } else { + throw new RuntimeException("Unimplemented/Invalid"); + } + } + Text nodelet = caret.getContainer().cast(); + int offset = caret.getTextOffset() + distance; + while (offset < 0) { + nodelet = htmlView.getPreviousSibling(nodelet).cast(); + if (nodelet == null || !DomHelper.isTextNode(nodelet)) { + throw new RuntimeException("Action ran off end of text node"); + } + offset += nodelet.getLength(); + } + while (offset > nodelet.getLength()) { + offset -= nodelet.getLength(); + nodelet = htmlView.getPreviousSibling(nodelet).cast(); + if (nodelet == null || !DomHelper.isTextNode(nodelet)) { + throw new RuntimeException("Action ran off end of text node"); + } + } + setCaret(Point.inText((Node)nodelet, offset)); + } + + private void setCaret(Point<Node> point) { + caret = point; + } + + public static Object move(Point<Node> caret) { + return new Object[]{Action.MOVE, caret, caret}; + } + +//TODO(danilatos): Support non-collapsed selections +// public static Object move(Point<Node> start, Point<Node> end) { +// return new Object[]{Action.MOVE, start, end}; +// } + + public static Object type(String text) { + return new Object[]{Action.TYPE, text}; + } + + public static Object del() { + return del(1); + } + + public static Object del(int len) { + return new Object[]{Action.DELETE, len}; + } + + public static Object bksp() { + return bksp(1); + } + + public static Object bksp(int len) { + return new Object[]{Action.BACKSPACE, len}; + } + + public static Object split() { + return new Object[]{Action.SPLIT}; + } + + @Override + public Point<Node> getSelectionEnd() { + return caret; + } + + @Override + public Point<Node> getSelectionStart() { + return caret; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/MockTypingSink.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/MockTypingSink.java b/test/org/waveprotocol/wave/client/editor/testing/MockTypingSink.java new file mode 100644 index 0000000..f82bcc2 --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/MockTypingSink.java @@ -0,0 +1,98 @@ +/** + * 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.client.editor.testing; + +import org.waveprotocol.wave.client.editor.RestrictedRange; +import org.waveprotocol.wave.client.editor.content.ContentNode; +import org.waveprotocol.wave.client.editor.content.ContentTextNode; +import org.waveprotocol.wave.client.editor.extract.TypingExtractor.TypingSink; + +import junit.framework.TestCase; + +import org.waveprotocol.wave.model.document.util.Point; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A mocked typing sink providing assertion methods. + * + * @author [email protected] (Daniel Danilatos) + */ +public class MockTypingSink implements TypingSink { + static class Op { } + static class Ins extends Op { + Point<ContentNode> start; + String text; + private Ins(Point<ContentNode> start, String text) { + this.start = start; + this.text = text; + } + } + static class Del extends Op { + Point<ContentNode> start; + int deleteSize; + private Del(Point<ContentNode> start, int deleteSize) { + this.start = start; + this.deleteSize = deleteSize; + } + } + + final List<Op> expectedOps = new ArrayList<Op>(); + final Set<ContentTextNode> affectedNodes = new HashSet<ContentTextNode>(); + boolean finished = true; + + public void expectDelete(Point<ContentNode> start, int deleteSize) { + expectedOps.add(new Del(start, deleteSize)); + } + + public void expectInsert(Point<ContentNode> start, String text) { + expectedOps.add(new Ins(start, text)); + } + + public void expectFinished() { + TestCase.assertTrue(finished && expectedOps.isEmpty()); + } + + @Override + public void typingReplace(Point<ContentNode> start, int length, String text, + RestrictedRange<ContentNode> range) { + + if (length > 0) { + Del delOp = (Del)expectedOps.remove(0); + TestCase.assertEquals(delOp.start, start); + TestCase.assertEquals(delOp.deleteSize, length); + } + + if (text.length() > 0) { + Ins insOp = (Ins)expectedOps.remove(0); + TestCase.assertEquals(insOp.start, start); + TestCase.assertEquals(insOp.text, text); + } + + // TODO(danilatos): Test the range is correct + finished = true; + } + + @Override + public void aboutToFlush() { } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/StubDocumentOperationSink.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/StubDocumentOperationSink.java b/test/org/waveprotocol/wave/client/editor/testing/StubDocumentOperationSink.java new file mode 100644 index 0000000..0e05705 --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/StubDocumentOperationSink.java @@ -0,0 +1,38 @@ +/** + * 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.client.editor.testing; + +import org.waveprotocol.wave.model.document.operation.DocOp; +import org.waveprotocol.wave.model.operation.SilentOperationSink; + +/** + * For testing + * @author [email protected] (Daniel Danilatos) + */ +public class StubDocumentOperationSink implements SilentOperationSink<DocOp> { + + /** Handy instance */ + public static final StubDocumentOperationSink INSTANCE = new StubDocumentOperationSink(); + + @Override + public void consume(DocOp op) { + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/StubSelectionHelper.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/StubSelectionHelper.java b/test/org/waveprotocol/wave/client/editor/testing/StubSelectionHelper.java new file mode 100644 index 0000000..d32fe8a --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/StubSelectionHelper.java @@ -0,0 +1,90 @@ +/** + * 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.client.editor.testing; + +import org.waveprotocol.wave.client.editor.content.ContentNode; +import org.waveprotocol.wave.client.editor.content.ContentRange; +import org.waveprotocol.wave.client.editor.content.FocusedContentRange; +import org.waveprotocol.wave.client.editor.selection.content.SelectionHelper; +import org.waveprotocol.wave.model.document.util.FocusedRange; +import org.waveprotocol.wave.model.document.util.Point; +import org.waveprotocol.wave.model.document.util.Range; + +/** + * Empty implementation. Suitable for subclassing. + * + * @author [email protected] (Daniel Danilatos) + */ +public class StubSelectionHelper implements SelectionHelper { + + public static final StubSelectionHelper INSTANCE = new StubSelectionHelper(); + + public void clearSelection() { + + } + + public FocusedContentRange getSelectionPoints() { + return null; + } + + @Override + public ContentRange getOrderedSelectionPoints() { + return null; + } + + public FocusedRange getSelectionRange() { + return null; + } + + @Override + public Range getOrderedSelectionRange() { + return null; + } + + public void setCaret(Point<ContentNode> caret) { + + } + + @Override + public void setCaret(int caret) { + + } + + public void setSelectionPoints(Point<ContentNode> start, + Point<ContentNode> end) { + + } + + public void setSelectionRange(FocusedRange selection) { + + } + + public Point<ContentNode> getFirstValidSelectionPoint() { + return null; + } + + public Point<ContentNode> getLastValidSelectionPoint() { + return null; + } + + public boolean isValidSelectionPoint(Point<ContentNode> cp) { + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/TestEditors.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/TestEditors.java b/test/org/waveprotocol/wave/client/editor/testing/TestEditors.java new file mode 100644 index 0000000..5de369b --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/TestEditors.java @@ -0,0 +1,92 @@ +/** + * 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.client.editor.testing; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; + +import org.waveprotocol.wave.client.editor.Editor; +import org.waveprotocol.wave.client.editor.EditorSettings; +import org.waveprotocol.wave.client.editor.EditorStaticDeps; +import org.waveprotocol.wave.client.editor.Editors; +import org.waveprotocol.wave.client.editor.ElementHandlerRegistry; +import org.waveprotocol.wave.client.editor.content.ContentDocument; +import org.waveprotocol.wave.client.editor.content.PainterRegistry; +import org.waveprotocol.wave.client.editor.content.Registries; +import org.waveprotocol.wave.client.editor.content.Renderer; +import org.waveprotocol.wave.client.editor.content.misc.StyleAnnotationHandler; +import org.waveprotocol.wave.client.editor.content.paragraph.LineRendering; +import org.waveprotocol.wave.client.editor.keys.KeyBindingRegistry; +import org.waveprotocol.wave.client.widget.popup.simple.Popup; +import org.waveprotocol.wave.model.conversation.Blips; +import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema; +import org.waveprotocol.wave.model.document.util.AnnotationRegistry; +import org.waveprotocol.wave.model.document.util.LineContainers; + +/** + * Utility to set up basic editors for integration testing. + * + * @author [email protected] (Daniel Danilatos) + */ +public class TestEditors { + /** + * Gets a realistic editor for testing. + */ + public static Editor getMinimalEditor() { + registerHandlers(Editor.ROOT_REGISTRIES); + + EditorStaticDeps.setPopupProvider(Popup.LIGHTWEIGHT_POPUP_PROVIDER); + Editor editor = Editors.create(); + editor.init(Editor.ROOT_REGISTRIES, KeyBindingRegistry.NONE, EditorSettings.DEFAULT); + editor.setEditing(true); + return editor; + } + + private static void registerHandlers(Registries registries) { + AnnotationRegistry annotationRegistry = registries.getAnnotationHandlerRegistry(); + PainterRegistry paintRegistry = registries.getPaintRegistry(); + ElementHandlerRegistry elementHandlerRegistry = registries.getElementHandlerRegistry(); + + LineContainers.setTopLevelContainerTagname(Blips.BODY_TAGNAME); + LineRendering.registerContainer(Blips.BODY_TAGNAME, elementHandlerRegistry); + TestInlineDoodad.register(elementHandlerRegistry); + StyleAnnotationHandler.register(registries); + } + + /** For testing purposes only. */ + public static ContentDocument createTestDocument() { + ContentDocument doc = new ContentDocument(DocumentSchema.NO_SCHEMA_CONSTRAINTS); + Registries registries = Editor.ROOT_REGISTRIES.createExtension(); + for (String t : new String[] {"q", "a", "b", "c", "x"}) { + final String tag = t; + registries.getElementHandlerRegistry().registerRenderer(tag, + new Renderer() { + @Override + public Element createDomImpl(Renderable element) { + return element.setAutoAppendContainer(Document.get().createElement(tag)); + } + }); + } + doc.setRegistries(registries); + Editor editor = getMinimalEditor(); + editor.setContent(doc); + return doc; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/TestInlineDoodad.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/TestInlineDoodad.java b/test/org/waveprotocol/wave/client/editor/testing/TestInlineDoodad.java new file mode 100644 index 0000000..a9ce076 --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/TestInlineDoodad.java @@ -0,0 +1,54 @@ +/** + * 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.client.editor.testing; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.SpanElement; + +import org.waveprotocol.wave.client.editor.ElementHandlerRegistry; +import org.waveprotocol.wave.client.editor.RenderingMutationHandler; +import org.waveprotocol.wave.client.editor.extract.PasteFormatRenderers; + +/** + * Inline doodad for testing. + * + */ +public class TestInlineDoodad { + private static class TestRenderer extends RenderingMutationHandler { + @Override + public Element createDomImpl(Renderable element) { + SpanElement domElement = Document.get().createSpanElement(); + return element.setAutoAppendContainer(domElement); + } + } + + public static final String FULL_TAGNAME = "span"; + + public static void register(ElementHandlerRegistry handlerRegistry) { + register(handlerRegistry, FULL_TAGNAME); + } + + public static void register(ElementHandlerRegistry handlerRegistry, String tagName) { + RenderingMutationHandler renderingMutationHandler = new TestRenderer(); + handlerRegistry.registerRenderingMutationHandler(tagName, renderingMutationHandler); + handlerRegistry.registerNiceHtmlRenderer(tagName, PasteFormatRenderers.SHALLOW_CLONE_RENDERER); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/editor/testing/Testing.gwt.xml ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/editor/testing/Testing.gwt.xml b/test/org/waveprotocol/wave/client/editor/testing/Testing.gwt.xml new file mode 100644 index 0000000..6ad55cc --- /dev/null +++ b/test/org/waveprotocol/wave/client/editor/testing/Testing.gwt.xml @@ -0,0 +1,31 @@ +<?xml version='1.0'?> +<!-- + + 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. + +--> + +<module> +<inherits name="com.google.gwt.user.User" /> +<inherits name="org.waveprotocol.wave.client.editor.Editor" /> +<inherits name="org.waveprotocol.wave.client.widget.popup.simple.Simple" /> +<inherits name="org.waveprotocol.wave.model.conversation.Conversation" /> +<inherits name="org.waveprotocol.wave.model.schema.conversation.Conversation" /> +<source path=""/> + +</module> http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/testing/UndercurrentHarness.gwt.xml ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/testing/UndercurrentHarness.gwt.xml b/test/org/waveprotocol/wave/client/testing/UndercurrentHarness.gwt.xml new file mode 100644 index 0000000..c5d6167 --- /dev/null +++ b/test/org/waveprotocol/wave/client/testing/UndercurrentHarness.gwt.xml @@ -0,0 +1,53 @@ +<?xml version='1.0'?> +<!-- + + 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. + +--> + +<module rename-to="waveharness"> + <inherits name="com.google.gwt.user.User" /> + <inherits name="org.waveprotocol.wave.client.Client" /> + <inherits name="org.waveprotocol.wave.common.bootstrap.FlagConstants" /> + <inherits name="org.waveprotocol.wave.model.conversation.Conversation" /> + <inherits name="org.waveprotocol.wave.model.conversation.Testing" /> + <inherits name="org.waveprotocol.wave.model.util.Util" /> + <inherits name="com.google.common.collect.Collect"/> + <inherits name="org.waveprotocol.box.stat.Stat" /> + <inherits name='org.waveprotocol.box.webclient.stat.Stat'/> + + <entry-point class="org.waveprotocol.wave.client.testing.UndercurrentHarness" /> + <source path=""/> + + <!-- Those comments beginning with the words "comment" or "Uncomment" have + special meaning to the PRESUBMIT.py script. --> + <!-- comment out the next line to build all client types --> + + <set-property name="loglevel" value="none"/> + <set-property name="compiler.emulatedStack" value="false"/> + + <!-- For make CSS obfuscation more pretty --> + <set-configuration-property name="CssResource.style" value="pretty"/> + + <!-- This linker is required for superdev mode --> + <add-linker name="xsiframe" /> + + <!-- collapse all properties to decrease the amount of time spend compiling --> + <collapse-all-properties /> + +</module> http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/testing/UndercurrentHarness.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/testing/UndercurrentHarness.java b/test/org/waveprotocol/wave/client/testing/UndercurrentHarness.java new file mode 100644 index 0000000..48a6753 --- /dev/null +++ b/test/org/waveprotocol/wave/client/testing/UndercurrentHarness.java @@ -0,0 +1,380 @@ +/** + * 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.client.testing; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.RepeatingCommand; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.Command; + +import org.waveprotocol.wave.client.StageOne; +import org.waveprotocol.wave.client.StageThree; +import org.waveprotocol.wave.client.StageTwo; +import org.waveprotocol.wave.client.StageZero; +import org.waveprotocol.wave.client.Stages; +import org.waveprotocol.wave.client.common.safehtml.SafeHtmlBuilder; +import org.waveprotocol.wave.client.common.util.AsyncHolder; +import org.waveprotocol.wave.client.concurrencycontrol.MuxConnector; +import org.waveprotocol.wave.client.doodad.DoodadInstallers; +import org.waveprotocol.wave.client.doodad.attachment.AttachmentManagerProvider; +import org.waveprotocol.wave.client.doodad.attachment.testing.FakeAttachmentsManager; +import org.waveprotocol.wave.client.util.ClientFlags; +import org.waveprotocol.wave.client.util.NullTypedSource; +import org.waveprotocol.wave.client.util.OverridingTypedSource; +import org.waveprotocol.wave.client.wavepanel.impl.toolbar.color.ComplexColorPicker; +import org.waveprotocol.wave.client.wavepanel.impl.toolbar.color.SampleCustomColorPicker; +import org.waveprotocol.wave.common.bootstrap.FlagConstants; +import org.waveprotocol.wave.concurrencycontrol.channel.WaveViewService; +import org.waveprotocol.wave.model.conversation.Conversation; +import org.waveprotocol.wave.model.conversation.ConversationBlip; +import org.waveprotocol.wave.model.conversation.ConversationThread; +import org.waveprotocol.wave.model.conversation.ConversationView; +import org.waveprotocol.wave.model.conversation.WaveBasedConversationView; +import org.waveprotocol.wave.model.document.util.XmlStringBuilder; +import org.waveprotocol.wave.model.id.IdGenerator; +import org.waveprotocol.wave.model.schema.SchemaProvider; +import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas; +import org.waveprotocol.wave.model.testing.BasicFactories; +import org.waveprotocol.wave.model.testing.FakeIdGenerator; +import org.waveprotocol.wave.model.util.CollectionUtils; +import org.waveprotocol.wave.model.util.ReadableStringMap.ProcV; +import org.waveprotocol.wave.model.util.StringMap; +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.ReadableWaveletData; +import org.waveprotocol.wave.model.wave.data.WaveViewData; +import org.waveprotocol.wave.model.wave.data.impl.WaveViewDataImpl; +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; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl.WaveletConfigurator; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl.WaveletFactory; + +/** + * Kicks off some initial actions for development purposes. + * + */ +public class UndercurrentHarness implements EntryPoint { + + private UndercurrentHarness() { + } + + private static boolean loaded; + + /** + * Runs the harness script. + */ + @Override + public void onModuleLoad() { + AttachmentManagerProvider.init(new FakeAttachmentsManager()); + if (loaded) { + return; + } + loaded = true; + + final Timeline timeline = new Timeline(); + new Stages() { + + @Override + protected AsyncHolder<StageZero> createStageZeroLoader() { + return new StageZero.DefaultProvider() { + + @Override + protected void onStageInit() { + timeline.add("stage0_start"); + } + + @Override + protected void onStageLoaded() { + timeline.add("stage0_end"); + } + + @Override + protected UncaughtExceptionHandler createUncaughtExceptionHandler() { + return GWT.getUncaughtExceptionHandler(); + } + }; + } + + @Override + protected AsyncHolder<StageOne> createStageOneLoader(StageZero zero) { + return new StageOne.DefaultProvider(zero) { + @Override + protected void onStageInit() { + timeline.add("stage1_start"); + } + + @Override + protected void onStageLoaded() { + timeline.add("stage1_end"); + } + + @Override + protected Element createWaveHolder() { + return Document.get().getElementById("initialHtml"); + } + }; + } + + @Override + protected AsyncHolder<StageTwo> createStageTwoLoader(StageOne one) { + return new StageTwo.DefaultProvider(one, null) { + + @Override + protected void onStageInit() { + timeline.add("stage2_start"); + } + + @Override + protected void onStageLoaded() { + timeline.add("stage2_end"); + } + + @Override + protected void fetchWave(Accessor<WaveViewData> whenReady) { + timeline.add("fakewave_start"); + WaveViewData fake = WaveFactory.create(getDocumentRegistry()); + timeline.add("fakewave_end"); + whenReady.use(fake); + } + + @Override + protected ParticipantId createSignedInUser() { + return ParticipantId.ofUnsafe("[email protected]"); + } + + @Override + protected String createSessionId() { + return "session"; + } + + @Override + protected MuxConnector createConnector() { + return new MuxConnector() { + @Override + public void connect(Command whenOpened) { + if (whenOpened != null) { + whenOpened.execute(); + } + } + + @Override + public void close() { + // Ignore + } + }; + } + + @Override + protected WaveViewService createWaveViewService() { + // The vacuous MuxConnector should avoid the need for a + // communication layer. + throw new UnsupportedOperationException(); + } + + @Override + protected SchemaProvider createSchemas() { + return new ConversationSchemas(); + } + }; + } + + @Override + protected AsyncHolder<StageThree> createStageThreeLoader(StageTwo two) { + ClientFlags.resetWithSourceForTesting(OverridingTypedSource.of(new NullTypedSource()) + .withBoolean(FlagConstants.ENABLE_UNDERCURRENT_EDITING, true) + .build()); + + // Only for test additional color pickers + new SampleCustomColorPicker(ComplexColorPicker.getInstance()); + + return new StageThree.DefaultProvider(two) { + @Override + protected void onStageInit() { + timeline.add("stage3_start"); + } + + @Override + protected void onStageLoaded() { + timeline.add("stage3_end"); + } + }; + } + }.load(new Command() { + @Override + public void execute() { + showInfo(timeline); + } + }); + } + + /** + * Populates the info box. Continuously reports which element has browser + * focus, and reports timing information for the stage loading. + * + * @param timeline timeline to report + */ + private static void showInfo(Timeline timeline) { + Element timeBox = Document.get().getElementById("timeline"); + timeline.dump(timeBox); + + Scheduler.get().scheduleFixedDelay(new RepeatingCommand() { + private final Element activeBox = Document.get().getElementById("active"); + + @Override + public boolean execute() { + Element e = getActiveElement(); + String text = (e != null ? e.getTagName() + " id:" + e.getId() : "none"); + activeBox.setInnerText(text); + return true; + } + + private native Element getActiveElement() /*-{ + return $doc.activeElement; + }-*/; + }, 1000); + } + + /** + * Creates a sample wave with a conversation in it. + */ + private final static class WaveFactory { + + /** + * Creates a sample wave. + * + * @param docFactory factory/registry for documents in the wave + * @return the wave state of the sample wave. + */ + public static WaveViewDataImpl create(DocumentFactory<?> docFactory) { + // Create a sample wave. + WaveViewData sampleData = createSampleWave(); + + // Now build one that has the same setup state as that required by + // undercurrent (complex issue with the per-document output sinks). + WaveViewDataImpl newData = WaveViewDataImpl.create(sampleData.getWaveId()); + WaveletDataImpl.Factory copier = WaveletDataImpl.Factory.create(docFactory); + for (ReadableWaveletData src : sampleData.getWavelets()) { + WaveletDataImpl copied = copier.create(src); + for (ParticipantId p : src.getParticipants()) { + copied.addParticipant(p); + } + copied.setVersion(copied.getVersion()); + copied.setHashedVersion(src.getHashedVersion()); + copied.setLastModifiedTime(src.getLastModifiedTime()); + newData.addWavelet(copied); + } + return newData; + } + + /** @return a sample wave with a conversation in it. */ + private static WaveViewData createSampleWave() { + final ParticipantId sampleAuthor = ParticipantId.ofUnsafe("[email protected]"); + IdGenerator gen = FakeIdGenerator.create(); + final WaveViewDataImpl waveData = WaveViewDataImpl.create(gen.newWaveId()); + final DocumentFactory<?> docFactory = BasicFactories.fakeDocumentFactory(); + final ObservableWaveletData.Factory<?> waveletDataFactory = + new ObservableWaveletData.Factory<WaveletDataImpl>() { + private final ObservableWaveletData.Factory<WaveletDataImpl> inner = + WaveletDataImpl.Factory.create(docFactory); + + @Override + public WaveletDataImpl create(ReadableWaveletData data) { + WaveletDataImpl wavelet = inner.create(data); + waveData.addWavelet(wavelet); + return wavelet; + } + }; + WaveletFactory<OpBasedWavelet> waveletFactory = BasicFactories + .opBasedWaveletFactoryBuilder() + .with(waveletDataFactory) + .with(sampleAuthor) + .build(); + + WaveViewImpl<?> wave = WaveViewImpl.create( + waveletFactory, waveData.getWaveId(), gen, sampleAuthor, WaveletConfigurator.ADD_CREATOR); + + // Build a conversation in that wave. + ConversationView v = WaveBasedConversationView.create(wave, gen); + Conversation c = v.createRoot(); + ConversationThread root = c.getRootThread(); + sampleReply(root.appendBlip()); + write(root.appendBlip()); + write(root.appendBlip()); + write(root.appendBlip()); + + return waveData; + } + + private static void write(ConversationBlip blip) { + org.waveprotocol.wave.model.document.Document d = blip.getContent(); + d.emptyElement(d.getDocumentElement()); + d.appendXml(XmlStringBuilder.createFromXmlString("<body><line></line>Hello World</body>")); + } + + private static void sampleReply(ConversationBlip blip) { + write(blip); + ConversationThread thread = blip.addReplyThread(8); + write(thread.appendBlip()); + } + + private static void biggerSampleReply(ConversationBlip blip) { + write(blip); + ConversationThread thread = blip.addReplyThread(); + sampleReply(thread.appendBlip()); + sampleReply(thread.appendBlip()); + write(thread.appendBlip()); + } + } + + private static class Timeline { + private final StringMap<Integer> events = CollectionUtils.createStringMap(); + private final Duration duration = new Duration(); + + void add(String name) { + events.put(name, duration.elapsedMillis()); + } + + void dump(Element timeBox) { + final SafeHtmlBuilder timeHtml = new SafeHtmlBuilder(); + timeHtml.appendHtmlConstant("<table cellpadding='0' cellspacing='0'>"); + events.each(new ProcV<Integer>() { + @Override + public void apply(String key, Integer value) { + timeHtml.appendHtmlConstant("<tr><td>"); + timeHtml.appendEscaped(key); + timeHtml.appendHtmlConstant(":</td><td>"); + timeHtml.appendEscaped("" + value); + timeHtml.appendHtmlConstant("</td></tr>"); + } + }); + timeHtml.appendHtmlConstant("</table>"); + timeBox.setInnerHTML(timeHtml.toSafeHtml().asString()); + + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/70f39328/test/org/waveprotocol/wave/client/testing/public/UndercurrentHarness.html ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/client/testing/public/UndercurrentHarness.html b/test/org/waveprotocol/wave/client/testing/public/UndercurrentHarness.html new file mode 100644 index 0000000..a7ac1a3 --- /dev/null +++ b/test/org/waveprotocol/wave/client/testing/public/UndercurrentHarness.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<!-- + 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. +--> +<html> + <head> + <meta http-equiv="Content-type" content="text/html;charset=UTF-8"> + <meta name="gwt:property" content="locale=en"> + <title>Undercurrent Harness</title> + <style type='text/css'> + /* Full screen box. */ + body { + position: absolute; + margin: 0; + padding: 0; + left: 0; + right: 0; + top: 0; + bottom: 0; + + /* + * Arial seems to be the only non-ugly sans-serif font that renders roughly the same on both + * Chrome and Firefox. e.g., 'sans-serif' on Firefox is a nicer mystery font, because it is + * wider, but Chrome binds 'sans-serif' to 'arial'. + */ + font-family: arial; + font-size: small; + } + + /* Fixed-width and horizontally centered, in a way that works on IE. */ + #initialHtml { + position: absolute; + left: 50%; + right: 50%; + top: 4em; /* Enough space for the header. */ + bottom: 4em; + /* Expand out of the center line, aiming for ~70-75 character blip widths. */ + margin-left: -250px; + margin-right: -250px; + border: 1px solid lightGray; + } + + #info { + position: absolute; + z-index: 100; + right: 20px; + top: 20px; + padding: 0.2em; + width: 10em; + border: 1px solid black; + background-color: lightGray; + } + + #timeline { + margin-left: 1em; + } + + </style> + </head> + <body> + <h2>Undercurrent Test Harness</h2> + <div id='initialHtml'></div> + <div id='info'> + Active: <span id='active'></span><br/> + Timing: + <div id='timeline'></div> + </div> + <script type='text/javascript' src='waveharness.nocache.js'> + </script> + </body> +</html>
