GriffonTestFXClassRule.java
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2008-2017 the original author or authors.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package griffon.javafx.test;
019 
020 import griffon.core.ApplicationEvent;
021 import griffon.core.RunnableWithArgs;
022 import griffon.core.env.Environment;
023 import griffon.exceptions.GriffonException;
024 import griffon.javafx.JavaFXGriffonApplication;
025 import javafx.stage.Window;
026 import org.awaitility.Duration;
027 import org.codehaus.griffon.runtime.core.DefaultGriffonApplication;
028 import org.codehaus.griffon.runtime.javafx.TestJavaFXGriffonApplication;
029 import org.junit.rules.TestRule;
030 import org.junit.runner.Description;
031 import org.junit.runners.model.Statement;
032 import org.testfx.api.FxToolkit;
033 
034 import javax.annotation.Nonnull;
035 import javax.annotation.Nullable;
036 import java.util.concurrent.TimeoutException;
037 
038 import static griffon.javafx.test.TestContext.getTestContext;
039 import static griffon.util.GriffonNameUtils.requireNonBlank;
040 import static java.util.Objects.requireNonNull;
041 import static java.util.concurrent.TimeUnit.SECONDS;
042 import static org.awaitility.Awaitility.await;
043 
044 /**
045  * A JUnit Rule that starts the application once per test class.
046  * Use it in combination with {@code FunctionalJavaFXRunner}.
047  *
048  @author Andres Almiray
049  @see griffon.javafx.test.FunctionalJavaFXRunner
050  @since 2.3.0
051  */
052 public class GriffonTestFXClassRule extends TestFX implements TestRule {
053     protected String windowName;
054     protected Duration timeout;
055     protected String[] startupArgs;
056     protected Class<? extends TestJavaFXGriffonApplication> applicationClass;
057     protected JavaFXGriffonApplication application;
058     private boolean failures = false;
059 
060     public GriffonTestFXClassRule(@Nonnull String windowName) {
061         this(TestJavaFXGriffonApplication.class, windowName, new Duration(10, SECONDS), DefaultGriffonApplication.EMPTY_ARGS);
062     }
063 
064     public GriffonTestFXClassRule(@Nonnull String windowName, @Nonnull Duration timeout) {
065         this(TestJavaFXGriffonApplication.class, windowName, timeout, DefaultGriffonApplication.EMPTY_ARGS);
066     }
067 
068     public GriffonTestFXClassRule(@Nonnull Class<? extends TestJavaFXGriffonApplication> applicationClass, @Nonnull String windowName) {
069         this(applicationClass, windowName, new Duration(10, SECONDS), DefaultGriffonApplication.EMPTY_ARGS);
070     }
071 
072     public GriffonTestFXClassRule(@Nonnull Class<? extends TestJavaFXGriffonApplication> applicationClass, @Nonnull String windowName, @Nonnull Duration timeout) {
073         this(applicationClass, windowName, timeout, DefaultGriffonApplication.EMPTY_ARGS);
074     }
075 
076     public GriffonTestFXClassRule(@Nonnull Class<? extends TestJavaFXGriffonApplication> applicationClass, @Nonnull String windowName, @Nonnull String[] startupArgs) {
077         this(applicationClass, windowName, new Duration(10, SECONDS), DefaultGriffonApplication.EMPTY_ARGS);
078     }
079 
080     public GriffonTestFXClassRule(@Nonnull Class<? extends TestJavaFXGriffonApplication> applicationClass, @Nonnull String windowName, @Nonnull Duration timeout, @Nonnull String[] startupArgs) {
081         this.applicationClass = requireNonNull(applicationClass, "Argument 'applicationClass' must not be null");
082         this.windowName = requireNonBlank(windowName, "Argument 'windowName' cannot be blank");
083         this.timeout = requireNonNull(timeout, "Argument 'timeout' cannot be blank");
084         requireNonNull(startupArgs, "Argument 'startupArgs' must not be null");
085         this.startupArgs = new String[startupArgs.length];
086         System.arraycopy(startupArgs, 0this.startupArgs, 0, startupArgs.length);
087         if (!Environment.isSystemSet()) {
088             System.setProperty(Environment.KEY, Environment.TEST.getName());
089         }
090     }
091 
092     public void setup() {
093         initialize();
094 
095         try {
096             FxToolkit.registerPrimaryStage();
097 
098             application = (JavaFXGriffonApplicationFxToolkit.setupApplication(applicationClass);
099             WindowShownHandler startingWindow = new WindowShownHandler(windowName);
100             application.getEventRouter().addEventListener(ApplicationEvent.WINDOW_SHOWN.getName(), startingWindow);
101 
102             await().timeout(timeout).until(startingWindow::isShowing);
103         catch (TimeoutException e) {
104             throw new GriffonException("An error occurred while starting up the application", e);
105         }
106     }
107 
108     public void cleanup() {
109         if (application != null) {
110             application.shutdown();
111             try {
112                 FxToolkit.cleanupApplication(application);
113             catch (TimeoutException e) {
114                 throw new GriffonException("An error occurred while shutting down the application", e);
115             finally {
116                 application = null;
117             }
118         }
119     }
120 
121     @Override
122     public Statement apply(Statement base, Description description) {
123         return new Statement() {
124             @Override
125             public void evaluate() throws Throwable {
126                 setup();
127                 try {
128                     base.evaluate();
129                 finally {
130                     cleanup();
131                 }
132             }
133         };
134     }
135 
136     public void injectMembers(@Nonnull Object target) {
137         requireNonNull(target, "Argument 'target' must not be null");
138         application.getInjector().injectMembers(target);
139     }
140 
141     public boolean hasFailures() {
142         return failures;
143     }
144 
145     public void setFailures(boolean failures) {
146         this.failures = failures;
147     }
148 
149     @Nullable
150     public <W extends Window> W managedWindow(@Nonnull String name) {
151         return (Wapplication.getWindowManager().findWindow(name);
152     }
153 
154     protected void initialize() {
155         getTestContext().setWindowName(windowName);
156     }
157 
158     private static class WindowShownHandler implements RunnableWithArgs {
159         private final String windowName;
160         private boolean showing;
161 
162         private WindowShownHandler(String windowName) {
163             this.windowName = windowName;
164         }
165 
166         public boolean isShowing() {
167             return showing;
168         }
169 
170         @Override
171         public void run(Object... args) {
172             if (args != null && args.length > && args[0instanceof CharSequence) {
173                 showing = windowName.equals(String.valueOf(args[0]));
174             }
175         }
176     }
177 }