/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.client.editor.event; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import com.google.common.base.Preconditions; import junit.framework.TestCase; import org.mockito.InOrder; import org.waveprotocol.wave.client.editor.constants.BrowserEvents; import org.waveprotocol.wave.client.editor.event.CompositionEventHandler.CompositionListener; import org.waveprotocol.wave.client.scheduler.testing.FakeTimerService; import org.waveprotocol.wave.common.logging.LoggerBundle; /** * @author danilatos@google.com (Daniel Danilatos) * */ public class CompositionEventHandlerTest extends TestCase { private final Object eventToken = new Object(); private final Object eventToken2 = new Object(); private final CompositionListener<Object> listener = mockListener(); private final FakeTimerService timer = new FakeTimerService(); private CompositionEventHandler<Object> handler = new CompositionEventHandler<Object>( timer, listener, LoggerBundle.NOP_IMPL, true); private final CompositionEventHandler<Object> handler2 = new CompositionEventHandler<Object>( timer, listener, LoggerBundle.NOP_IMPL, false); @SuppressWarnings("unchecked") private CompositionListener<Object> mockListener() { return mock(CompositionListener.class); } @SuppressWarnings("unchecked") private void nothingElseUpToNow() { handler.checkAppComposing(); verifyNoMoreInteractions(listener); reset(listener); } private void tick() { timer.tick(handler.compositionEndDelay + 1); } private void verifyStart(Object ... token) { Preconditions.checkArgument(token.length <= 1); verify(listener, times(1)).compositionStart(token.length == 1 ? token[0] : eventToken); nothingElseUpToNow(); } private void verifyUpdate() { verify(listener, times(1)).compositionUpdate(); nothingElseUpToNow(); } private void verifyEnd() { verify(listener, times(1)).compositionEnd(); nothingElseUpToNow(); } private void doStartUpdateEnd() { handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONSTART); verifyStart(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONUPDATE); verifyUpdate(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONEND); nothingElseUpToNow(); } private void verifyDone() { timer.tick(500); nothingElseUpToNow(); assertFalse(handler.delayAfterTextInput); } private void doSomeMoreTestsToCheckSaneState() { testBasicTiming(); testMergesInterleavedSecondCycle(); testDoesntMergeSecondCycleAfterTextInput(); testDoesntEndWithTextInput(); } public void testBasicTiming() { doStartUpdateEnd(); tick(); verifyEnd(); verifyDone(); } public void testMergesInterleavedSecondCycle() { doStartUpdateEnd(); handler.handleCompositionEvent(eventToken2, BrowserEvents.COMPOSITIONSTART); nothingElseUpToNow(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONEND); nothingElseUpToNow(); tick(); verifyEnd(); verifyDone(); } public void testDoesntMergeSecondCycleAfterTextInput() { assertFalse(handler.delayAfterTextInput); doStartUpdateEnd(); assertFalse(handler.delayAfterTextInput); handler.handleCompositionEvent(eventToken, BrowserEvents.TEXTINPUT); assertTrue(handler.delayAfterTextInput); nothingElseUpToNow(); handler.handleCompositionEvent(eventToken2, BrowserEvents.COMPOSITIONSTART); assertFalse(handler.delayAfterTextInput); InOrder ordered = inOrder(listener); ordered.verify(listener).compositionEnd(); ordered.verify(listener).compositionStart(eventToken2); nothingElseUpToNow(); handler.handleCompositionEvent(eventToken2, BrowserEvents.COMPOSITIONEND); nothingElseUpToNow(); tick(); verifyEnd(); verifyDone(); } public void testHandlesInterleavedOtherEvent() { doStartUpdateEnd(); handler.handleOtherEvent(); verifyEnd(); handler.handleOtherEvent(); verifyDone(); } public void testIgnoresOtherEventDuringComposition() { handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONSTART); verifyStart(); handler.handleOtherEvent(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONUPDATE); verifyUpdate(); handler.handleOtherEvent(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONEND); nothingElseUpToNow(); tick(); verify(listener).compositionEnd(); handler.handleOtherEvent(); verifyDone(); } public void testDealsWithDupCompositionStartEvents() { handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONSTART); verifyStart(); handler.handleOtherEvent(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONUPDATE); verifyUpdate(); // dup start doesn't notify handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONSTART); nothingElseUpToNow(); handler.handleOtherEvent(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONUPDATE); verifyUpdate(); handler.handleOtherEvent(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONEND); nothingElseUpToNow(); tick(); verifyEnd(); handler.handleOtherEvent(); doSomeMoreTestsToCheckSaneState(); } public void testDealsWithDupCompositionEndEvents() { doStartUpdateEnd(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONEND); nothingElseUpToNow(); tick(); verifyEnd(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONEND); nothingElseUpToNow(); doSomeMoreTestsToCheckSaneState(); } public void testDoesntEndWithTextInput() { doStartUpdateEnd(); handler.handleCompositionEvent(eventToken, BrowserEvents.TEXTINPUT); assertTrue(handler.delayAfterTextInput); nothingElseUpToNow(); tick(); verifyEnd(); // Non-composition text input is ignored assertFalse(handler.handleCompositionEvent(eventToken, BrowserEvents.TEXTINPUT)); assertFalse(handler.delayAfterTextInput); verifyDone(); } // This might be the way webkit does things in the future public void testHandlesTextInputBeforeCompositionEnd() { handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONSTART); verifyStart(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONUPDATE); verifyUpdate(); assertFalse(handler.handleCompositionEvent(eventToken, BrowserEvents.TEXTINPUT)); nothingElseUpToNow(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONEND); tick(); verifyEnd(); verifyDone(); } public void testIgnoresTimerDuringComposition() { doStartUpdateEnd(); handler.handleCompositionEvent(eventToken2, BrowserEvents.COMPOSITIONSTART); nothingElseUpToNow(); tick(); nothingElseUpToNow(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONEND); nothingElseUpToNow(); tick(); verifyEnd(); verifyDone(); } public void testSimpleMode() { handler = handler2; handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONSTART); verifyStart(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONUPDATE); verifyUpdate(); handler.handleCompositionEvent(eventToken, BrowserEvents.COMPOSITIONEND); verifyEnd(); verifyDone(); } }