/***************************************************************************
* Copyright 2006-2016 by Christian Ihle *
* contact@kouchat.net *
* *
* This file is part of KouChat. *
* *
* KouChat is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version. *
* *
* KouChat is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with KouChat. *
* If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
package net.usikkert.kouchat.android.controller;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import net.usikkert.kouchat.android.R;
import net.usikkert.kouchat.android.chatwindow.AndroidPrivateChatWindow;
import net.usikkert.kouchat.android.chatwindow.AndroidUserInterface;
import net.usikkert.kouchat.android.service.ChatService;
import net.usikkert.kouchat.android.service.ChatServiceBinder;
import net.usikkert.kouchat.misc.User;
import net.usikkert.kouchat.util.TestUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowHandler;
import org.robolectric.shadows.ShadowIntent;
import org.robolectric.util.ActivityController;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.internal.view.menu.ActionMenuItem;
import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import android.content.Intent;
import android.view.KeyEvent;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
/**
* Test of {@link PrivateChatController}.
*
* @author Christian Ihle
*/
@Config(reportSdk = 10)
@RunWith(RobolectricTestRunner.class)
public class PrivateChatControllerTest {
private ActivityController<PrivateChatController> activityController;
private PrivateChatController controller;
private ControllerUtils controllerUtils;
private User vivi;
private AndroidUserInterface ui;
private AndroidPrivateChatWindow chatWindow;
@Before
public void setUp() {
activityController = Robolectric.buildActivity(PrivateChatController.class);
controller = activityController.get();
vivi = new User("Vivi", 1234);
ui = mock(AndroidUserInterface.class);
chatWindow = mock(AndroidPrivateChatWindow.class);
when(ui.getUser(1234)).thenReturn(vivi);
doAnswer(withChatWindowForVivi(chatWindow)).when(ui).createPrivChat(vivi);
final ChatServiceBinder serviceBinder = mock(ChatServiceBinder.class);
when(serviceBinder.getAndroidUserInterface()).thenReturn(ui);
Robolectric.getShadowApplication().setComponentNameAndServiceForBindService(null, serviceBinder);
final Intent intent = new Intent(Robolectric.application, PrivateChatController.class);
intent.putExtra("userCode", 1234);
activityController.withIntent(intent);
controllerUtils = TestUtils.setFieldValueWithMock(controller, "controllerUtils", ControllerUtils.class);
}
@Test
public void onCreateShouldBindChatServiceToSetAndroidUserInterface() {
activityController.create();
assertSame(ui, TestUtils.getFieldValue(controller, AndroidUserInterface.class, "androidUserInterface"));
final ShadowIntent startedServiceIntent =
Robolectric.shadowOf(Robolectric.getShadowApplication().getNextStartedService());
assertEquals(ChatService.class, startedServiceIntent.getIntentClass());
}
@Test
public void onCreateShouldEnableUpInActionBar() {
activityController.create();
final ActionBar actionBar = controller.getSupportActionBar();
final int displayOptions = actionBar.getDisplayOptions();
assertTrue((displayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0);
}
@Test
public void onCreateShouldMakeLinksClickable() {
activityController.create();
final TextView privateChatView = (TextView) controller.findViewById(R.id.privateChatView);
verify(controllerUtils).makeLinksClickable(privateChatView);
}
@Test
public void onCreateShouldRequestFocusOnInputToOpenKeyboard() {
activityController.create();
final EditText privateChatInput = (EditText) controller.findViewById(R.id.privateChatInput);
assertTrue(privateChatInput.hasFocus());
}
@Test
public void onCreateWithNoUserShouldSetDefaultTitleAndNoSubtitle() {
activityController.withIntent(null);
activityController.create();
final ActionBar actionBar = controller.getSupportActionBar();
assertEquals("User not found - KouChat", actionBar.getTitle());
assertNull(actionBar.getSubtitle());
verifyZeroInteractions(chatWindow);
}
@Test
public void onCreateWithUserShouldSetTitleAndSubtitleFromChatWindow() {
doAnswer(withTitle("Title from chat window", "Subtitle from chat window")).when(chatWindow).updateTitle();
activityController.create();
final ActionBar actionBar = controller.getSupportActionBar();
assertEquals("Title from chat window", actionBar.getTitle());
assertEquals("Subtitle from chat window", actionBar.getSubtitle());
verify(chatWindow).updateTitle();
}
@Test
public void onCreateShouldRegisterKeyListenerThatSendsMessageAndClearsInputOnEnter() {
activityController.create();
final EditText privateChatInput = (EditText) controller.findViewById(R.id.privateChatInput);
privateChatInput.setText("Hello");
privateChatInput.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
verify(ui).sendPrivateMessage("Hello", vivi);
assertEquals("", privateChatInput.getText().toString());
}
@Test
public void onCreateShouldRegisterKeyListenerThatIgnoresOtherEvents() {
activityController.create();
final EditText privateChatInput = (EditText) controller.findViewById(R.id.privateChatInput);
privateChatInput.setText("Hello");
privateChatInput.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
privateChatInput.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE));
verify(ui, never()).sendPrivateMessage(anyString(), any(User.class));
assertEquals("Hello", privateChatInput.getText().toString());
}
@Test
public void onCreateShouldNotRegisterKeyListenerWhenUnknownUser() {
activityController.withIntent(null);
activityController.create();
final EditText privateChatInput = (EditText) controller.findViewById(R.id.privateChatInput);
privateChatInput.setText("Hello");
privateChatInput.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
verify(ui, never()).sendPrivateMessage(anyString(), any(User.class));
assertEquals("Hello", privateChatInput.getText().toString());
}
@Test
public void onCreateShouldSetupUserAndRegisterControllerWithChatWindow() {
assertNull(vivi.getPrivchat());
activityController.create();
verify(ui).getUser(1234);
verify(ui).createPrivChat(vivi);
final AndroidPrivateChatWindow viviChatWindow = (AndroidPrivateChatWindow) vivi.getPrivchat();
verify(viviChatWindow).registerPrivateChatController(controller);
assertSame(chatWindow, viviChatWindow);
}
@Test
public void onCreateShouldNotSetupUserAndRegisterControllerWithChatWindowWhenUnknownUser() {
activityController.withIntent(null);
activityController.create();
verify(ui).getUser(-1);
verify(ui, never()).createPrivChat(any(User.class));
assertNull(vivi.getPrivchat());
}
@Test
public void onCreateShouldNotifyAndroidUserInterfaceToResetNewPrivateMessageIcon() {
activityController.create();
verify(ui).activatedPrivChat(vivi);
}
@Test
public void onCreateShouldNotNotifyAndroidUserInterfaceWhenUnknownUser() {
activityController.withIntent(null);
activityController.create();
verify(ui, never()).activatedPrivChat(any(User.class));
}
@Test
public void isVisibleShouldBeTrueOnlyBetweenOnResumeAndOnPause() {
assertFalse(controller.isVisible());
activityController.create();
assertFalse(controller.isVisible());
activityController.resume();
assertTrue(controller.isVisible());
activityController.pause();
assertFalse(controller.isVisible());
activityController.destroy();
assertFalse(controller.isVisible());
}
@Test
public void onResumeShouldNotifyAndroidUserInterfaceToResetNewPrivateMessageIcon() {
activityController.create();
reset(ui); // Also happens during onCreate()
activityController.resume();
verify(ui).activatedPrivChat(vivi);
}
@Test
public void onResumeShouldNotNotifyAndroidUserInterfaceWhenUnknownUser() {
activityController.withIntent(null);
activityController.create();
activityController.resume();
verify(ui, never()).activatedPrivChat(any(User.class));
}
@Test
public void onResumeShouldNotFailIfServiceHasNotBeenBoundYet() {
controller.onResume();
verify(ui, never()).activatedPrivChat(any(User.class));
}
@Test
public void onDestroyShouldUnregister() {
activityController.create();
// Replacing with mocks for easier verification
final AndroidPrivateChatWindow privateChatWindow =
TestUtils.setFieldValueWithMock(controller, "privateChatWindow", AndroidPrivateChatWindow.class);
final EditText privateChatInput = TestUtils.setFieldValueWithMock(controller, "privateChatInput", EditText.class);
final TextView privateChatView = TestUtils.setFieldValueWithMock(controller, "privateChatView", TextView.class);
activityController.destroy();
verify(privateChatWindow).unregisterPrivateChatController();
verify(privateChatInput).setOnKeyListener(null);
verify(controllerUtils).removeReferencesToTextViewFromText(privateChatView);
verify(controllerUtils).removeReferencesToTextViewFromText(privateChatInput);
assertEquals(1, Robolectric.getShadowApplication().getUnboundServiceConnections().size());
}
@Test
public void onDestroyShouldSetAllFieldsToNull() {
activityController.create();
assertTrue(TestUtils.allFieldsHaveValue(controller));
activityController.destroy();
assertTrue(TestUtils.allFieldsAreNull(controller));
}
@Test
public void onDestroyShouldSetDestroyedToTrue() {
activityController.create();
assertFalse(TestUtils.getFieldValue(controller, Boolean.class, "destroyed"));
activityController.destroy();
assertTrue(TestUtils.getFieldValue(controller, Boolean.class, "destroyed"));
}
@Test
public void onDestroyShouldNotFailIfServiceHasNotBeenBound() {
activityController.create();
TestUtils.setFieldValue(controller, "privateChatWindow", null);
TestUtils.setFieldValue(controller, "user", null);
TestUtils.setFieldValue(controller, "androidUserInterface", null);
activityController.destroy();
assertEquals(0, Robolectric.getShadowApplication().getUnboundServiceConnections().size());
}
@Test
public void appendToPrivateChatShouldAppendAndScrollToBottomIfInputHasFocus() {
activityController.create();
final EditText privateChatInput = (EditText) controller.findViewById(R.id.privateChatInput);
final TextView privateChatView = (TextView) controller.findViewById(R.id.privateChatView);
final ScrollView privateChatScroll = (ScrollView) controller.findViewById(R.id.privateChatScroll);
privateChatView.setText("Original text");
privateChatInput.requestFocus();
assertTrue(privateChatInput.hasFocus());
controller.appendToPrivateChat("\nSome text");
assertEquals("Original text\nSome text", privateChatView.getText().toString());
verify(controllerUtils).scrollTextViewToBottom(privateChatView, privateChatScroll);
}
@Test
public void appendToPrivateChatShouldOnlyAppendIfInputLacksFocus() {
activityController.create();
final EditText privateChatInput = (EditText) controller.findViewById(R.id.privateChatInput);
final TextView privateChatView = (TextView) controller.findViewById(R.id.privateChatView);
privateChatView.setText("Original text");
privateChatView.requestFocus();
assertFalse(privateChatInput.hasFocus());
controller.appendToPrivateChat("\nSome other text");
assertEquals("Original text\nSome other text", privateChatView.getText().toString());
verify(controllerUtils, never()).scrollTextViewToBottom(any(TextView.class), any(ScrollView.class));
}
@Test
public void appendToPrivateChatShouldDoNothingIfDestroyed() {
activityController.create();
final TextView privateChatView = (TextView) controller.findViewById(R.id.privateChatView);
privateChatView.setText("Original text");
activityController.destroy();
controller.appendToPrivateChat("\nDon't append");
assertEquals("Original text", privateChatView.getText().toString());
verify(controllerUtils, never()).scrollTextViewToBottom(any(TextView.class), any(ScrollView.class));
}
@Test
public void updatePrivateChatShouldSetTextAndScrollToBottomIfNotDestroyed() {
activityController.create();
final TextView privateChatView = (TextView) controller.findViewById(R.id.privateChatView);
final ScrollView privateChatScroll = (ScrollView) controller.findViewById(R.id.privateChatScroll);
privateChatView.setText("Original text");
controller.updatePrivateChat("Set this text");
ShadowHandler.runMainLooperOneTask();
assertEquals("Set this text", privateChatView.getText().toString());
verify(controllerUtils).scrollTextViewToBottom(privateChatView, privateChatScroll);
}
@Test
public void updatePrivateChatShouldSetTextAndNotScrollToBottomIfDestroyed() {
activityController.create();
final TextView privateChatView = (TextView) controller.findViewById(R.id.privateChatView);
privateChatView.setText("Original text");
controller.updatePrivateChat("Set this text");
activityController.destroy(); // onDestroy() runs between setting the text and the delayed handler that scrolls
ShadowHandler.runMainLooperOneTask();
assertEquals("Set this text", privateChatView.getText().toString());
verify(controllerUtils, never()).scrollTextViewToBottom(any(TextView.class), any(ScrollView.class));
}
@Test
public void updateTitleAndSubtitleShouldSetTheSpecifiedTitleAndSubtitle() {
activityController.create();
final ActionBar actionBar = controller.getSupportActionBar();
controller.updateTitleAndSubtitle("This is the title", "This is the subtitle");
assertEquals("This is the title", actionBar.getTitle());
assertEquals("This is the subtitle", actionBar.getSubtitle());
}
@Test
public void dispatchKeyEventShouldDelegateToSuperClassFirst() {
activityController.create();
final EditText privateChatInput = TestUtils.setFieldValueWithMock(controller, "privateChatInput", EditText.class);
// Force ActionBarSherlock to respond to the back event
controller.startActionMode(new ActionMode.Callback() {
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) { return true; }
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) { return false; }
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { return false; }
public void onDestroyActionMode(final ActionMode mode) { }
});
assertTrue(controller.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)));
verifyZeroInteractions(privateChatInput); // Not delegating, and not requesting focus
}
@Test
public void dispatchKeyEventShouldDelegateToPrivateChatInputSecond() {
activityController.create();
final EditText privateChatInput = TestUtils.setFieldValueWithMock(controller, "privateChatInput", EditText.class);
final KeyEvent event1 = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A);
when(privateChatInput.dispatchKeyEvent(event1)).thenReturn(false);
assertFalse(controller.dispatchKeyEvent(event1));
verify(privateChatInput).dispatchKeyEvent(event1);
final KeyEvent event2 = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_B);
when(privateChatInput.dispatchKeyEvent(event2)).thenReturn(true);
assertTrue(controller.dispatchKeyEvent(event2));
verify(privateChatInput).dispatchKeyEvent(event2);
}
@Test
public void dispatchKeyEventShouldRequestFocusIfFocusIsMissingIfDelegatingToPrivateChatInput() {
activityController.create();
final EditText privateChatInput = TestUtils.setFieldValueWithMock(controller, "privateChatInput", EditText.class);
when(privateChatInput.hasFocus()).thenReturn(true);
controller.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A));
verify(privateChatInput, never()).requestFocus();
when(privateChatInput.hasFocus()).thenReturn(false);
controller.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A));
verify(privateChatInput).requestFocus();
}
@Test
public void onOptionsItemSelectedWithUpShouldGoBackToMainChat() {
activityController.create();
final boolean selected = controller.onOptionsItemSelected(createMenuItem(android.R.id.home));
assertTrue(selected);
final Intent startedActivityIntent = Robolectric.getShadowApplication().getNextStartedActivity();
final ShadowIntent startedActivityShadowIntent = Robolectric.shadowOf(startedActivityIntent);
assertEquals(MainChatController.class, startedActivityShadowIntent.getIntentClass());
}
@Test
public void onOptionsItemSelectedWithUnknownMenuItemShouldReturnFalse() {
activityController.create();
final boolean selected = controller.onOptionsItemSelected(createMenuItem(0));
assertFalse(selected);
}
@Test
public void sendPrivateMessageShouldSendToUserUsingAndroidUserInterface() {
activityController.create();
controller.sendPrivateMessage("A private message");
verify(ui).sendPrivateMessage("A private message", vivi);
}
@Test
public void sendPrivateMessageShouldNotSendIfMessageIsNull() {
activityController.create();
controller.sendPrivateMessage(null);
verify(ui, never()).sendPrivateMessage(anyString(), any(User.class));
}
@Test
public void sendPrivateMessageShouldNotSendIfMessageIsWhitespace() {
activityController.create();
controller.sendPrivateMessage(" ");
verify(ui, never()).sendPrivateMessage(anyString(), any(User.class));
}
private ActionMenuItem createMenuItem(final int menuItemId) {
return new ActionMenuItem(null, 0, menuItemId, 0, 0, "");
}
private Answer<Void> withChatWindowForVivi(final AndroidPrivateChatWindow privateChatWindow) {
return new Answer<Void>() {
@Override
public Void answer(final InvocationOnMock invocation) {
vivi.setPrivchat(privateChatWindow);
return null;
}
};
}
private Answer withTitle(final String title, final String subtitle) {
return new Answer<Void>() {
@Override
public Void answer(final InvocationOnMock invocation) {
controller.updateTitleAndSubtitle(title, subtitle);
return null;
}
};
}
}