package rmblworx.tools.timey.gui; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.ResourceBundle; import java.util.Vector; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.DatePicker; import javafx.scene.control.TextField; import javafx.stage.Stage; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import org.loadui.testfx.categories.TestFX; import org.loadui.testfx.utils.FXTestUtils; import rmblworx.tools.timey.gui.component.TimePicker; /* * Copyright 2014-2015 Christian Raue * MIT License http://opensource.org/licenses/mit-license.php */ /** * GUI-Tests für den Dialog zum Bearbeiten eines Alarms. * @author Christian Raue {@literal <christian.raue@gmail.com>} */ @Category(TestFX.class) public class AlarmEditDialogControllerTest extends FxmlGuiControllerTest { /** * Container für Elemente. */ private Scene scene; // GUI-Elemente private AlarmEditDialogController controller; private CheckBox alarmEnabledCheckbox; private DatePicker alarmDatePicker; private TimePicker alarmTimePicker; private TextField alarmDescriptionTextField; private Button alarmSelectSoundButton; private Button alarmNoSoundButton; private Button alarmPlaySoundButton; private Button alarmSaveButton; /** * Stage mocken, da das Schließen des echten Dialogs dafür sorgen würde, dass das Fenster (Stage) für andere Tests nicht mehr zur * Verfügung stünde. */ private Stage mockedDialogStage = mock(Stage.class); /** * {@inheritDoc} */ @Override protected final String getFxmlFilename() { return "AlarmEditDialog.fxml"; } @Before public final void setUp() { scene = stage.getScene(); controller = (AlarmEditDialogController) getController(); alarmEnabledCheckbox = (CheckBox) scene.lookup("#alarmEnabledCheckbox"); alarmDatePicker = (DatePicker) scene.lookup("#alarmDatePicker"); alarmTimePicker = (TimePicker) scene.lookup("#alarmTimePicker"); alarmDescriptionTextField = (TextField) scene.lookup("#alarmDescriptionTextField"); alarmSelectSoundButton = (Button) scene.lookup("#alarmSelectSoundButton"); alarmNoSoundButton = (Button) scene.lookup("#alarmNoSoundButton"); alarmPlaySoundButton = (Button) scene.lookup("#alarmPlaySoundButton"); alarmSaveButton = (Button) scene.lookup("#alarmSaveButton"); } @Test public final void testInitializedFields() throws IllegalAccessException { super.testFxmlInitializedFields(); } /** * Testet den Zustand der Steuerelemente nach Öffnen des Dialogs. */ @Test public final void testInitialState() { final TextField hoursTextField = (TextField) scene.lookup("#hoursTextField"); // Alarm vorgeben controller.setAlarm(new Alarm(DateTimeUtil.getLocalDateTimeForString("24.12.2014 12:00:00"), "Test")); FXTestUtils.awaitEvents(); // sicherstellen, dass Formularfelder korrekt gefüllt sind assertTrue(alarmEnabledCheckbox.isSelected()); assertEquals(DateTimeUtil.getLocalDateForString("24.12.2014"), alarmDatePicker.getValue()); assertEquals(DateTimeUtil.getLocalTimeForString("12:00:00"), alarmTimePicker.getValue()); assertEquals("12", hoursTextField.getText()); assertEquals("Test", alarmDescriptionTextField.getText()); assertEquals("kein Klingelton gewählt", alarmSelectSoundButton.getText()); assertTrue(alarmNoSoundButton.isDisabled()); assertTrue(alarmPlaySoundButton.isDisabled()); } /** * Testet das Speichern der Änderungen. */ @Test public final void testApplyChanges() { controller.setDialogStage(mockedDialogStage); // Alarm vorgeben final Alarm alarm = new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.1970 00:00:00"), "Bla"); controller.setAlarm(alarm); FXTestUtils.awaitEvents(); // deaktivieren click(alarmEnabledCheckbox); waitForThreads(); Platform.runLater(new Runnable() { public void run() { // Datum setzen alarmDatePicker.setValue(DateTimeUtil.getLocalDateForString("24.12.2014")); // Zeit setzen alarmTimePicker.setValue(DateTimeUtil.getLocalTimeForString("12:00:00")); // Beschreibung setzen alarmDescriptionTextField.setText("Test"); } }); FXTestUtils.awaitEvents(); // Sound setzen controller.setRingtone("Sound"); FXTestUtils.awaitEvents(); // Speichern-Schaltfläche betätigen click(alarmSaveButton); waitForThreads(); // sicherstellen, dass Dialog geschlossen worden wäre verify(mockedDialogStage, timeout(WAIT_FOR_EVENT)).close(); // sicherstellen, dass Alarm neue Werte hat assertFalse(alarm.isEnabled()); assertEquals(DateTimeUtil.getLocalDateTimeForString("24.12.2014 12:00:00"), alarm.getDateTime()); assertEquals("Test", alarm.getDescription()); assertEquals("Sound", alarm.getSound()); } /** * Testet Anzeige der Fehlermeldung bei leerem Datum. */ @Test public final void testErrorDateEmpty() { controller.setDialogStage(mockedDialogStage); controller.setExistingAlarms(Arrays.asList(new Alarm())); // Alarm vorgeben controller.setAlarm(new Alarm()); FXTestUtils.awaitEvents(); // Datum leeren Platform.runLater(new Runnable() { public void run() { alarmDatePicker.setValue(null); } }); FXTestUtils.awaitEvents(); final MessageHelper messageHelper = mock(MessageHelper.class); controller.getGuiHelper().setMessageHelper(messageHelper); // Speichern-Schaltfläche betätigen click(alarmSaveButton); waitForThreads(); // sicherstellen, dass nicht Dialog geschlossen worden wäre verify(mockedDialogStage, never()).close(); // sicherstellen, dass Fehlermeldung erscheint verify(messageHelper).showDialogMessage(anyString(), eq("Ein Datum muss angegeben werden.\n"), isA(ResourceBundle.class)); } /** * Testet Anzeige bzw. Ausbleiben von Fehlermeldungen beim Speichern eines Alarms. */ @Test public final void testErrorWhenSavingAlarm() { final List<DataErrors> testCases = new Vector<>(); // Anlegen/Bearbeiten eines Alarms mit Zeitstempel in Vergangenheit testCases.add(new DataErrors(1, "Der Alarm-Zeitpunkt muss in der Zukunft liegen.\n", null, new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.1970 00:00:00"), "Alarm"))); // Anlegen/Bearbeiten eines Alarms mit Zeitstempel in Zukunft testCases.add(new DataErrors(0, null, null, new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.2050 00:00:00"), "Alarm"))); // Anlegen/Bearbeiten eines inaktiven Alarms mit Zeitstempel in Vergangenheit testCases.add(new DataErrors(0, null, null, new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.1970 00:00:00"), "Alarm", null, false))); // Anlegen/Bearbeiten eines inaktiven Alarms mit Zeitstempel in Zukunft testCases.add(new DataErrors(0, null, null, new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.2050 00:00:00"), "Alarm", null, false))); // Anlegen eines Alarms mit identischem Zeitstempel testCases.add(new DataErrors(1, "Ein anderer Alarm mit demselben Zeitpunkt existiert bereits.\n", Arrays.asList(new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.2050 00:00:00"), "Alarm1")), new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.2050 00:00:00"), "Alarm2"))); // Anlegen eines Alarms mit unterschiedlichem Zeitstempel testCases.add(new DataErrors(0, null, Arrays.asList(new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.2050 00:00:00"), "Alarm1")), new Alarm(DateTimeUtil.getLocalDateTimeForString("11.11.2160 00:00:00"), "Alarm2"))); // Bearbeiten eines Alarms ohne Änderung des Zeitstempels final Alarm alarm = new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.2050 00:00:00"), "Alarm1"); // genau ein Objekt testCases.add(new DataErrors(0, null, Arrays.asList(alarm), alarm)); for (final DataErrors testCase : testCases) { reset(mockedDialogStage); controller.setDialogStage(mockedDialogStage); controller.setExistingAlarms(testCase.existingAlarms); controller.setAlarm(testCase.alarm); FXTestUtils.awaitEvents(); final MessageHelper messageHelper = mock(MessageHelper.class); controller.getGuiHelper().setMessageHelper(messageHelper); // Speichern-Schaltfläche betätigen click(alarmSaveButton); waitForThreads(); // sicherstellen, dass nicht Dialog geschlossen worden wäre verify(mockedDialogStage, testCase.numberOfCalls > 0 ? never() : times(1)).close(); // sicherstellen, dass Fehlermeldung erscheint bzw. nicht verify(messageHelper, times(testCase.numberOfCalls)).showDialogMessage(anyString(), testCase.errorMessage == null ? anyString() : eq(testCase.errorMessage), isA(ResourceBundle.class)); } } /** * Testet das Verwerfen der Änderungen. */ @Test public final void testCancel() { controller.setDialogStage(mockedDialogStage); // Alarm vorgeben final LocalDateTime now = LocalDateTime.now(); final Alarm alarm = new Alarm(now, "bla"); controller.setAlarm(alarm); FXTestUtils.awaitEvents(); // deaktivieren click(alarmEnabledCheckbox); waitForThreads(); Platform.runLater(new Runnable() { public void run() { // Datum setzen alarmDatePicker.setValue(DateTimeUtil.getLocalDateForString("24.12.2014")); // Zeit setzen alarmTimePicker.setValue(DateTimeUtil.getLocalTimeForString("12:00:00")); // Beschreibung setzen alarmDescriptionTextField.setText("Test"); } }); FXTestUtils.awaitEvents(); // Sound setzen controller.setRingtone("Sound"); FXTestUtils.awaitEvents(); // Abbrechen-Schaltfläche betätigen final Button alarmCancelButton = (Button) scene.lookup("#alarmCancelButton"); click(alarmCancelButton); waitForThreads(); // sicherstellen, dass Dialog geschlossen worden wäre verify(mockedDialogStage, timeout(WAIT_FOR_EVENT)).close(); // sicherstellen, dass Alarm ursprüngliche Werte hat assertTrue(alarm.isEnabled()); assertEquals(now, alarm.getDateTime()); assertEquals("bla", alarm.getDescription()); assertEquals(null, alarm.getSound()); } /** * Testet das Auswählen, Anhören und Löschen eines Klingeltons. */ @Test public final void testSoundSelection() { controller.setDialogStage(mockedDialogStage); // Alarm vorgeben final Alarm alarm = new Alarm(DateTimeUtil.getLocalDateTimeForString("01.01.1970 00:00:00"), "Bla"); controller.setAlarm(alarm); FXTestUtils.awaitEvents(); // Sound setzen controller.setRingtone("Sound"); FXTestUtils.awaitEvents(); assertFalse(alarmNoSoundButton.isDisabled()); assertFalse(alarmPlaySoundButton.isDisabled()); // Sound-Abspielen-Schaltfläche betätigen final AudioPlayer player = mock(AudioPlayer.class); controller.getGuiHelper().setAudioPlayer(player); click(alarmPlaySoundButton); waitForThreads(); verify(player).playInThread(isA(ThreadHelper.class), eq("Sound"), isA(Thread.UncaughtExceptionHandler.class)); // Sound-Löschen-Schaltfläche betätigen click(alarmNoSoundButton); waitForThreads(); // sicherstellen, dass nicht Dialog geschlossen worden wäre verify(mockedDialogStage, never()).close(); assertEquals("kein Klingelton gewählt", alarmSelectSoundButton.getText()); } private final class DataErrors { public final int numberOfCalls; public final String errorMessage; public final List<Alarm> existingAlarms; public final Alarm alarm; public DataErrors(final int numberOfCalls, final String messageMatcher, final List<Alarm> existingAlarms, final Alarm alarm) { this.numberOfCalls = numberOfCalls; this.errorMessage = messageMatcher; this.existingAlarms = existingAlarms; this.alarm = alarm; } } }