package com.faforever.client.chat; import com.faforever.client.net.ConnectionState; import com.faforever.client.test.AbstractPlainJavaFxTest; import com.faforever.client.user.UserService; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import javafx.beans.InvalidationListener; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.MapChangeListener; import javafx.scene.control.Tab; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.springframework.context.ApplicationContext; import org.springframework.util.ReflectionUtils; import org.testfx.util.WaitForAsyncUtils; import java.time.Instant; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import static com.natpryce.hamcrest.reflection.HasAnnotationMatcher.hasAnnotation; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; // TODO those unit tests need to be improved (missing verifications) public class ChatControllerTest extends AbstractPlainJavaFxTest { public static final String TEST_USER_NAME = "junit"; private static final String TEST_CHANNEL_NAME = "#testChannel"; private static final long TIMEOUT = 1000; private static final TimeUnit TIMEOUT_UNITS = TimeUnit.MILLISECONDS; @Mock private ChannelTabController channelTabController; @Mock private PrivateChatTabController privateChatTabController; @Mock private UserService userService; @Mock private ApplicationContext applicationContext; @Mock private ChatService chatService; @Mock private EventBus eventBus; @Captor private ArgumentCaptor<MapChangeListener<String, Channel>> channelsListener; @Captor private ArgumentCaptor<Consumer<ChatMessage>> onChannelMessageListenerCaptor; @Captor private ArgumentCaptor<Consumer<ChatMessage>> onPrivateMessageListenerCaptor; @Captor private ArgumentCaptor<MapChangeListener<String, ChatUser>> onUsersListenerCaptor; private ChatController instance; private SimpleObjectProperty<ConnectionState> connectionState; @Before public void setUp() throws Exception { instance = loadController("chat.fxml"); instance.userService = userService; instance.chatService = chatService; instance.applicationContext = applicationContext; instance.eventBus = eventBus; connectionState = new SimpleObjectProperty<>(); BooleanProperty loggedInProperty = new SimpleBooleanProperty(); when(applicationContext.getBean(PrivateChatTabController.class)).thenReturn(privateChatTabController); when(applicationContext.getBean(ChannelTabController.class)).thenReturn(channelTabController); when(userService.getUsername()).thenReturn(TEST_USER_NAME); when(userService.loggedInProperty()).thenReturn(loggedInProperty); when(chatService.connectionStateProperty()).thenReturn(connectionState); instance.postConstruct(); verify(chatService).addChannelsListener(channelsListener.capture()); } @Test public void testOnMessageForChannel() throws Exception { when(channelTabController.getRoot()).thenReturn(new Tab()); ChatMessage chatMessage = new ChatMessage(TEST_CHANNEL_NAME, Instant.now(), TEST_USER_NAME, "message"); CompletableFuture<ChatMessage> chatMessageCompletableFuture = new CompletableFuture<>(); doAnswer(invocation -> { chatMessageCompletableFuture.complete((ChatMessage) invocation.getArguments()[0]); return null; }).when(channelTabController).onChatMessage(chatMessage); verify(chatService).addOnMessageListener(onChannelMessageListenerCaptor.capture()); onChannelMessageListenerCaptor.getValue().accept(chatMessage); chatMessageCompletableFuture.get(TIMEOUT, TIMEOUT_UNITS); verify(channelTabController).onChatMessage(chatMessage); } @Test public void testOnDisconnected() throws Exception { connectionState.set(ConnectionState.DISCONNECTED); } @Test public void testOnPrivateMessage() throws Exception { ChatMessage chatMessage = mock(ChatMessage.class); verify(chatService).addOnPrivateChatMessageListener(onPrivateMessageListenerCaptor.capture()); onPrivateMessageListenerCaptor.getValue().accept(chatMessage); // TODO assert something useful } @Test public void testGetRoot() throws Exception { assertThat(instance.getRoot(), is(instance.chatRoot)); assertThat(instance.getRoot().getParent(), is(nullValue())); } @Test(expected = IllegalStateException.class) public void testOpenPrivateMessageTabForUserNotOnApplicationThread() throws Exception { instance.onInitiatePrivateChatEvent(new InitiatePrivateChatEvent("user")); } @Test public void testOpenPrivateMessageTabForUser() throws Exception { when(privateChatTabController.getRoot()).thenReturn(new Tab()); WaitForAsyncUtils.waitForAsyncFx(TIMEOUT, () -> instance.onInitiatePrivateChatEvent(new InitiatePrivateChatEvent("user"))); } @Test public void testOpenPrivateMessageTabForSelf() throws Exception { when(privateChatTabController.getRoot()).thenReturn(new Tab()); instance.onInitiatePrivateChatEvent(new InitiatePrivateChatEvent(TEST_USER_NAME)); } @Test public void testOnChannelsJoinedRequest() throws Exception { when(channelTabController.getRoot()).thenReturn(new Tab()); channelJoined(TEST_CHANNEL_NAME); channelJoined(TEST_CHANNEL_NAME); connectionState.set(ConnectionState.DISCONNECTED); } @SuppressWarnings("unchecked") private void channelJoined(String channel) { MapChangeListener.Change<? extends String, ? extends Channel> testChannelChange = mock(MapChangeListener.Change.class); when(testChannelChange.getKey()).thenReturn(channel); channelsListener.getValue().onChanged(testChannelChange); } @Test @SuppressWarnings("unchecked") public void testOnJoinChannelButtonClicked() throws Exception { assertThat(instance.chatsTabPane.getTabs(), is(empty())); Tab tab = new Tab(); tab.setId(TEST_CHANNEL_NAME); when(channelTabController.getRoot()).thenReturn(tab); when(userService.getUsername()).thenReturn(TEST_USER_NAME); when(chatService.isDefaultChannel(TEST_CHANNEL_NAME)).thenReturn(false); doAnswer(invocation -> { MapChangeListener.Change<? extends String, ? extends Channel> change = mock(MapChangeListener.Change.class); when(change.wasAdded()).thenReturn(true); when(change.getValueAdded()).thenReturn(new Channel(invocation.getArgumentAt(0, String.class))); channelsListener.getValue().onChanged(change); return null; }).when(chatService).joinChannel(anyString()); instance.channelNameTextField.setText(TEST_CHANNEL_NAME); instance.onJoinChannelButtonClicked(); verify(chatService).joinChannel(TEST_CHANNEL_NAME); verify(chatService).addUsersListener(eq(TEST_CHANNEL_NAME), onUsersListenerCaptor.capture()); MapChangeListener.Change<? extends String, ? extends ChatUser> change = mock(MapChangeListener.Change.class); when(change.wasAdded()).thenReturn(true); when(change.getValueAdded()).thenReturn(new ChatUser(TEST_USER_NAME, null)); onUsersListenerCaptor.getValue().onChanged(change); CountDownLatch tabAddedLatch = new CountDownLatch(1); instance.chatsTabPane.getTabs().addListener((InvalidationListener) observable -> tabAddedLatch.countDown()); tabAddedLatch.await(2, TimeUnit.SECONDS); assertThat(instance.chatsTabPane.getTabs(), hasSize(1)); assertThat(instance.chatsTabPane.getTabs().get(0).getId(), is(TEST_CHANNEL_NAME)); } @Test public void testSubscribeAnnotations() { assertThat(ReflectionUtils.findMethod( ChatController.class, "onInitiatePrivateChatEvent", InitiatePrivateChatEvent.class), hasAnnotation(Subscribe.class)); } }