From 4980cefd298fc33068fa155801f6423b89a0a57f Mon Sep 17 00:00:00 2001 From: Sergey Petukhov Date: Tue, 12 Sep 2017 22:51:38 +0700 Subject: [PATCH] Introduce HistoryCallback --- .../sample/basic/BasicSampleActivity.java | 4 +- .../sample/helloworld/HelloWorldActivity.java | 14 +- .../IntentsSingleInstanceSampleActivity.java | 5 +- .../IntentsStandardSampleActivity.java | 5 +- .../multikey/MultiKeySampleActivity.java | 4 +- .../OrientationSampleActivity.java | 5 +- .../flow/sample/tree/TreeSampleActivity.java | 4 +- flow/src/main/java/flow/Flow.java | 54 ++- flow/src/main/java/flow/HistoryCallback.java | 17 + flow/src/main/java/flow/Installer.java | 18 +- .../flow/InternalLifecycleIntegration.java | 7 +- flow/src/test/java/flow/FlowTest.java | 389 +++++++++++------- flow/src/test/java/flow/ReentranceTest.java | 11 +- 13 files changed, 345 insertions(+), 192 deletions(-) create mode 100644 flow/src/main/java/flow/HistoryCallback.java diff --git a/flow-sample-basic/src/main/java/flow/sample/basic/BasicSampleActivity.java b/flow-sample-basic/src/main/java/flow/sample/basic/BasicSampleActivity.java index 31c3480..2510ece 100644 --- a/flow-sample-basic/src/main/java/flow/sample/basic/BasicSampleActivity.java +++ b/flow-sample-basic/src/main/java/flow/sample/basic/BasicSampleActivity.java @@ -38,8 +38,6 @@ public class BasicSampleActivity extends Activity { } @Override public void onBackPressed() { - if (!Flow.get(this).goBack()) { - super.onBackPressed(); - } + Flow.get(this).goBack(); } } diff --git a/flow-sample-helloworld/src/main/java/flow/sample/helloworld/HelloWorldActivity.java b/flow-sample-helloworld/src/main/java/flow/sample/helloworld/HelloWorldActivity.java index 9f33097..f0fc049 100644 --- a/flow-sample-helloworld/src/main/java/flow/sample/helloworld/HelloWorldActivity.java +++ b/flow-sample-helloworld/src/main/java/flow/sample/helloworld/HelloWorldActivity.java @@ -19,17 +19,21 @@ import android.app.Activity; import android.content.Context; import flow.Flow; +import flow.HistoryCallback; -public class HelloWorldActivity extends Activity { +public class HelloWorldActivity extends Activity implements HistoryCallback { @Override protected void attachBaseContext(Context baseContext) { - baseContext = Flow.configure(baseContext, this).install(); + baseContext = Flow.configure(baseContext, this).historyCallback(this).install(); super.attachBaseContext(baseContext); } @Override public void onBackPressed() { - if (!Flow.get(this).goBack()) { - super.onBackPressed(); - } + Flow.get(this).goBack(); + } + + @Override + public void onHistoryCleared() { + super.onBackPressed(); } } diff --git a/flow-sample-intents/src/main/java/flow/sample/intents/IntentsSingleInstanceSampleActivity.java b/flow-sample-intents/src/main/java/flow/sample/intents/IntentsSingleInstanceSampleActivity.java index ea77896..483bae7 100644 --- a/flow-sample-intents/src/main/java/flow/sample/intents/IntentsSingleInstanceSampleActivity.java +++ b/flow-sample-intents/src/main/java/flow/sample/intents/IntentsSingleInstanceSampleActivity.java @@ -19,6 +19,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; + import flow.Flow; public class IntentsSingleInstanceSampleActivity extends Activity { @@ -33,8 +34,6 @@ public class IntentsSingleInstanceSampleActivity extends Activity { } @Override public void onBackPressed() { - if (!Flow.get(this).goBack()) { - super.onBackPressed(); - } + Flow.get(this).goBack(); } } diff --git a/flow-sample-intents/src/main/java/flow/sample/intents/IntentsStandardSampleActivity.java b/flow-sample-intents/src/main/java/flow/sample/intents/IntentsStandardSampleActivity.java index 68e97e6..66db5c5 100644 --- a/flow-sample-intents/src/main/java/flow/sample/intents/IntentsStandardSampleActivity.java +++ b/flow-sample-intents/src/main/java/flow/sample/intents/IntentsStandardSampleActivity.java @@ -18,6 +18,7 @@ import android.app.Activity; import android.content.Context; + import flow.Flow; public class IntentsStandardSampleActivity extends Activity { @@ -28,8 +29,6 @@ public class IntentsStandardSampleActivity extends Activity { } @Override public void onBackPressed() { - if (!Flow.get(this).goBack()) { - super.onBackPressed(); - } + Flow.get(this).goBack(); } } diff --git a/flow-sample-multikey/src/main/java/flow/sample/multikey/MultiKeySampleActivity.java b/flow-sample-multikey/src/main/java/flow/sample/multikey/MultiKeySampleActivity.java index c16740e..ed17748 100644 --- a/flow-sample-multikey/src/main/java/flow/sample/multikey/MultiKeySampleActivity.java +++ b/flow-sample-multikey/src/main/java/flow/sample/multikey/MultiKeySampleActivity.java @@ -31,9 +31,7 @@ public class MultiKeySampleActivity extends AppCompatActivity { } @Override public void onBackPressed() { - if (!getFlow().goBack()) { - super.onBackPressed(); - } + getFlow().goBack(); } private final class Changer implements KeyChanger { diff --git a/flow-sample-orientation-lock/src/main/java/flow/sample/orientation/OrientationSampleActivity.java b/flow-sample-orientation-lock/src/main/java/flow/sample/orientation/OrientationSampleActivity.java index 61aacab..47622a9 100644 --- a/flow-sample-orientation-lock/src/main/java/flow/sample/orientation/OrientationSampleActivity.java +++ b/flow-sample-orientation-lock/src/main/java/flow/sample/orientation/OrientationSampleActivity.java @@ -19,6 +19,7 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; + import flow.Flow; public class OrientationSampleActivity extends Activity { @@ -37,8 +38,6 @@ public class OrientationSampleActivity extends Activity { } @Override public void onBackPressed() { - if (!Flow.get(this).goBack()) { - super.onBackPressed(); - } + Flow.get(this).goBack(); } } diff --git a/flow-sample-tree/src/main/java/flow/sample/tree/TreeSampleActivity.java b/flow-sample-tree/src/main/java/flow/sample/tree/TreeSampleActivity.java index a23022b..9926a66 100644 --- a/flow-sample-tree/src/main/java/flow/sample/tree/TreeSampleActivity.java +++ b/flow-sample-tree/src/main/java/flow/sample/tree/TreeSampleActivity.java @@ -49,9 +49,7 @@ public class TreeSampleActivity extends AppCompatActivity { } @Override public void onBackPressed() { - if (!Flow.get(this).goBack()) { - super.onBackPressed(); - } + Flow.get(this).goBack(); } private final class Changer implements KeyChanger { diff --git a/flow/src/main/java/flow/Flow.java b/flow/src/main/java/flow/Flow.java index e96704d..7508ef3 100644 --- a/flow/src/main/java/flow/Flow.java +++ b/flow/src/main/java/flow/Flow.java @@ -24,6 +24,7 @@ import android.support.annotation.Nullable; import android.view.View; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -119,6 +120,7 @@ public static void addHistory(@NonNull Intent intent, @NonNull History history, private HistoryFilter historyFilter = new NotPersistentHistoryFilter(); private Dispatcher dispatcher; private PendingTraversal pendingTraversal; + private HistoryCallback historyCallback; private List tearDownKeys = new ArrayList<>(); private final KeyManager keyManager; @@ -179,6 +181,10 @@ void setDispatcher(@NonNull Dispatcher dispatcher, final boolean restore) { } } + void setHistoryCallback(@NonNull HistoryCallback historyCallback) { + this.historyCallback = historyCallback; + } + /** * Remove the dispatcher. A noop if the given dispatcher is not the current one. *

@@ -282,30 +288,27 @@ public void set(@NonNull final Object newTopKey) { } /** - * Go back one key. Typically called from {@link Activity#onBackPressed()}, with - * the return value determining whether or not to call super. E.g. - *

-   * public void onBackPressed() {
-   *   if (!Flow.get(this).goBack()) {
-   *     super.onBackPressed();
-   *   }
-   * }
-   * 
- * - * @return false if going back is not possible. + * Go back one key. Typically called from {@link Activity#onBackPressed()}. + * If there is no way to go back, {@link HistoryCallback#onHistoryCleared()} would be triggered. + * Use {@link Installer#historyCallback(HistoryCallback)} to provide your own + * clearHistory implementation. By default, {@link Activity#finish()} would be called. */ - @CheckResult public boolean goBack() { + public void goBack() { boolean canGoBack = history.size() > 1 || (pendingTraversal != null && pendingTraversal.state != TraversalState.FINISHED); - if (!canGoBack) return false; + if (!canGoBack) { + historyCallback.onHistoryCleared(); + return; + } move(new PendingTraversal() { @Override void doExecute() { - if (history.size() <= 1) { - // The history shrank while this op was pending. It happens, let's - // no-op. See lengthy discussions: - // https://github.com/square/flow/issues/195 - // https://github.com/square/flow/pull/197 + if (history.size() == 0) { + throw new IllegalStateException("goBack() on empty history"); + } + if (history.size() == 1) { + // https://github.com/square/flow/issues/264 + pendingTraversal.clearHistory(); return; } @@ -315,7 +318,6 @@ public void set(@NonNull final Object newTopKey) { dispatch(newHistory, Direction.BACKWARD); } }); - return true; } private void move(PendingTraversal pendingTraversal) { @@ -438,6 +440,20 @@ final void execute() { doExecute(); } + final void clearHistory() { + // Note: history top will be cleared in onDestroy() call + final Iterator it = tearDownKeys.iterator(); + while (it.hasNext()) { + keyManager.tearDown(it.next()); + it.remove(); + } + keyManager.clearStatesExcept(Collections.emptyList()); + next = null; + pendingTraversal = null; + state = TraversalState.FINISHED; + historyCallback.onHistoryCleared(); + } + /** * Must be synchronous and end with a call to {@link #dispatch} or {@link * #onTraversalCompleted()}. diff --git a/flow/src/main/java/flow/HistoryCallback.java b/flow/src/main/java/flow/HistoryCallback.java new file mode 100644 index 0000000..b5b2cc8 --- /dev/null +++ b/flow/src/main/java/flow/HistoryCallback.java @@ -0,0 +1,17 @@ +package flow; + +import android.app.Activity; + +/** + * History callback, should be implemented to track history events. + * + * @author sdelaysam + */ + +public interface HistoryCallback { + /** + * Called by Flow when history cleared. + * Default implementation would call {@link Activity#finish()} + */ + void onHistoryCleared(); +} diff --git a/flow/src/main/java/flow/Installer.java b/flow/src/main/java/flow/Installer.java index b2026da..596eda1 100644 --- a/flow/src/main/java/flow/Installer.java +++ b/flow/src/main/java/flow/Installer.java @@ -32,6 +32,7 @@ public final class Installer { private KeyParceler parceler; private Object defaultKey; private Dispatcher dispatcher; + private HistoryCallback historyCallback; Installer(Context baseContext, Activity activity) { this.baseContext = baseContext; @@ -53,6 +54,11 @@ public final class Installer { return this; } + @NonNull public Installer historyCallback(@Nullable HistoryCallback historyCallback) { + this.historyCallback = historyCallback; + return this; + } + /** * Applies a factory when creating a Context associated with a given key. * @@ -73,13 +79,23 @@ public final class Installer { dispatcher = KeyDispatcher.configure(activity, new DefaultKeyChanger(activity)) // .build(); } + HistoryCallback historyCallback = this.historyCallback; + if (historyCallback == null) { + historyCallback = new HistoryCallback() { + @Override + public void onHistoryCleared() { + activity.finish(); + } + }; + } + final Object defState = defaultKey == null ? "Hello, World!" : defaultKey; final History defaultHistory = History.single(defState); final Application app = (Application) baseContext.getApplicationContext(); final KeyManager keyManager = new KeyManager(contextFactories); InternalLifecycleIntegration.install(app, activity, parceler, defaultHistory, dispatcher, - keyManager); + keyManager, historyCallback); return new InternalContextWrapper(baseContext, activity); } } diff --git a/flow/src/main/java/flow/InternalLifecycleIntegration.java b/flow/src/main/java/flow/InternalLifecycleIntegration.java index 7750ebe..295b6f4 100644 --- a/flow/src/main/java/flow/InternalLifecycleIntegration.java +++ b/flow/src/main/java/flow/InternalLifecycleIntegration.java @@ -53,7 +53,8 @@ public final class InternalLifecycleIntegration extends Fragment { static void install(final Application app, final Activity activity, @Nullable final KeyParceler parceler, final History defaultHistory, - final Dispatcher dispatcher, final KeyManager keyManager) { + final Dispatcher dispatcher, final KeyManager keyManager, + final HistoryCallback historyCallback) { app.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity a, Bundle savedInstanceState) { if (a == activity) { @@ -69,6 +70,8 @@ static void install(final Application app, final Activity activity, } // We always replace the dispatcher because it frequently references the Activity. fragment.dispatcher = dispatcher; + // Same with history callback, most likely it will reference the Activity + fragment.historyCallback = historyCallback; fragment.intent = a.getIntent(); if (newFragment) { activity.getFragmentManager() // @@ -106,6 +109,7 @@ static void install(final Application app, final Activity activity, History defaultHistory; Dispatcher dispatcher; Intent intent; + HistoryCallback historyCallback; private boolean dispatcherSet; public InternalLifecycleIntegration() { @@ -150,6 +154,7 @@ void onNewIntent(Intent intent) { } else { flow.setDispatcher(dispatcher, true); } + flow.setHistoryCallback(historyCallback); dispatcherSet = true; } diff --git a/flow/src/test/java/flow/FlowTest.java b/flow/src/test/java/flow/FlowTest.java index 9a6aa96..73e2820 100644 --- a/flow/src/test/java/flow/FlowTest.java +++ b/flow/src/test/java/flow/FlowTest.java @@ -30,6 +30,9 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.MockitoAnnotations.initMocks; public class FlowTest { @@ -55,6 +58,7 @@ static class Tres { final TestKey noPersist = new NoPersist(); @Mock KeyManager keyManager; + @Mock HistoryCallback historyCallback; History lastStack; Direction lastDirection; @@ -106,6 +110,7 @@ void assertDispatching(Object newTop) { History history = History.single(new Uno()); Flow flow = new Flow(keyManager, history); flow.setDispatcher(new FlowDispatcher()); + flow.setHistoryCallback(historyCallback); flow.set(new Dos()); assertThat(lastStack.top()).isInstanceOf(Dos.class); @@ -115,15 +120,18 @@ void assertDispatching(Object newTop) { assertThat(lastStack.top()).isInstanceOf(Tres.class); assertThat(lastDirection).isSameAs(Direction.FORWARD); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isInstanceOf(Dos.class); assertThat(lastDirection).isSameAs(Direction.BACKWARD); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isInstanceOf(Uno.class); assertThat(lastDirection).isSameAs(Direction.BACKWARD); - assertThat(flow.goBack()).isFalse(); + flow.goBack(); + verify(historyCallback, times(1)).onHistoryCleared(); } @Test public void historyChangesAfterListenerCall() { @@ -158,14 +166,18 @@ public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback on Flow flow = new Flow(keyManager, history); flow.setDispatcher(new FlowDispatcher()); + flow.setHistoryCallback(historyCallback); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isEqualTo(baker); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isEqualTo(able); - assertThat(flow.goBack()).isFalse(); + flow.goBack(); + verify(historyCallback, times(1)).onHistoryCleared(); } @Test public void setHistoryWorks() { @@ -173,15 +185,18 @@ public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback on Flow flow = new Flow(keyManager, history); FlowDispatcher dispatcher = new FlowDispatcher(); flow.setDispatcher(dispatcher); + flow.setHistoryCallback(historyCallback); History newHistory = History.emptyBuilder().pushAll(Arrays.asList(charlie, delta)).build(); flow.setHistory(newHistory, Direction.FORWARD); assertThat(lastDirection).isSameAs(Direction.FORWARD); assertThat(lastStack.top()).isSameAs(delta); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isSameAs(charlie); - assertThat(flow.goBack()).isFalse(); + flow.goBack(); + verify(historyCallback, times(1)).onHistoryCleared(); } @Test public void setObjectGoesBack() { @@ -189,6 +204,7 @@ public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback on History.emptyBuilder().pushAll(Arrays.asList(able, baker, charlie, delta)).build(); Flow flow = new Flow(keyManager, history); flow.setDispatcher(new FlowDispatcher()); + flow.setHistoryCallback(historyCallback); assertThat(history.size()).isEqualTo(4); @@ -197,21 +213,25 @@ public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback on assertThat(lastStack.size()).isEqualTo(3); assertThat(lastDirection).isEqualTo(Direction.BACKWARD); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isEqualTo(baker); assertThat(lastDirection).isEqualTo(Direction.BACKWARD); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isEqualTo(able); assertThat(lastDirection).isEqualTo(Direction.BACKWARD); - assertThat(flow.goBack()).isFalse(); + flow.goBack(); + verify(historyCallback, times(1)).onHistoryCleared(); } @Test public void setObjectToMissingObjectPushes() { History history = History.emptyBuilder().pushAll(Arrays.asList(able, baker)).build(); Flow flow = new Flow(keyManager, history); flow.setDispatcher(new FlowDispatcher()); + flow.setHistoryCallback(historyCallback); assertThat(history.size()).isEqualTo(2); flow.set(charlie); @@ -219,14 +239,18 @@ public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback on assertThat(lastStack.size()).isEqualTo(3); assertThat(lastDirection).isEqualTo(Direction.FORWARD); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isEqualTo(baker); assertThat(lastDirection).isEqualTo(Direction.BACKWARD); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isEqualTo(able); assertThat(lastDirection).isEqualTo(Direction.BACKWARD); - assertThat(flow.goBack()).isFalse(); + + flow.goBack(); + verify(historyCallback, times(1)).onHistoryCleared(); } @Test public void setObjectKeepsOriginal() { @@ -410,6 +434,7 @@ static class Picky { .build(); Flow flow = new Flow(keyManager, history); flow.setDispatcher(new FlowDispatcher()); + flow.setHistoryCallback(historyCallback); assertThat(history.size()).isEqualTo(4); @@ -418,15 +443,18 @@ static class Picky { assertThat(lastStack.size()).isEqualTo(3); assertThat(lastDirection).isEqualTo(Direction.BACKWARD); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isEqualTo(new Picky("Baker")); assertThat(lastDirection).isEqualTo(Direction.BACKWARD); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); assertThat(lastStack.top()).isEqualTo(new Picky("Able")); assertThat(lastDirection).isEqualTo(Direction.BACKWARD); - assertThat(flow.goBack()).isFalse(); + flow.goBack(); + verify(historyCallback, times(1)).onHistoryCleared(); } @Test public void incorrectFlowGetUsage() { @@ -480,161 +508,230 @@ static class Picky { } @Test - public void testComplexFlow() { + public void shouldTerminateInPendingTraversal() { AsyncDispatcher dispatcher = Mockito.spy(new AsyncDispatcher()); History history = History.single(able); Flow flow = new Flow(keyManager, history); flow.setDispatcher(dispatcher); + flow.setHistoryCallback(historyCallback); dispatcher.fire(); - // WELCOME->PROJECTS (replaceTop FORWARD) - flow.replaceTop(baker, Direction.FORWARD); - // forward to projects list complete - dispatcher.fire(); - dispatcher.assertIdle(); - assertThat(flow.getHistory().top()).isEqualTo(baker); - assertThat(flow.getHistory().size()).isEqualTo(1); - - // PROJECTS->DETAILS (set) - flow.set(charlie); - - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); - - // forward to project details complete - dispatcher.fire(); - dispatcher.assertDispatching(able); - assertThat(flow.getHistory().top()).isEqualTo(charlie); - assertThat(flow.getHistory().size()).isEqualTo(2); - - // WELCOME->PROJECTS (replaceTop FORWARD) - flow.replaceTop(baker, Direction.FORWARD); - - // forward to welcome complete - dispatcher.fire(); - dispatcher.assertDispatching(baker); - assertThat(flow.getHistory().top()).isEqualTo(able); - assertThat(flow.getHistory().size()).isEqualTo(1); - - // WELCOME->PROJECTS (replaceTop FORWARD) - flow.replaceTop(baker, Direction.FORWARD); - - Mockito.reset(dispatcher); - // forward to projects list complete + flow.set(baker); dispatcher.fire(); - Mockito.verify(dispatcher, Mockito.times(2)).fire(); dispatcher.assertIdle(); assertThat(flow.getHistory().top()).isEqualTo(baker); - assertThat(flow.getHistory().size()).isEqualTo(1); - - // PROJECTS->DETAILS (set) - flow.set(charlie); - - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); - - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); - - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); - - // forward to project details complete - dispatcher.fire(); - dispatcher.assertDispatching(able); - assertThat(flow.getHistory().top()).isEqualTo(charlie); assertThat(flow.getHistory().size()).isEqualTo(2); - // WELCOME->PROJECTS (replaceTop FORWARD) - flow.replaceTop(baker, Direction.FORWARD); - - Mockito.reset(dispatcher); - // forward to welcome complete - dispatcher.fire(); - Mockito.verify(dispatcher, Mockito.times(3)).fire(); - dispatcher.assertDispatching(baker); - assertThat(flow.getHistory().top()).isEqualTo(able); - assertThat(flow.getHistory().size()).isEqualTo(1); + flow.replaceHistory(History.single(charlie), Direction.REPLACE); + flow.goBack(); - // WELCOME->PROJECTS (replaceTop FORWARD) - flow.replaceTop(baker, Direction.FORWARD); + // check clearHistory not called immediately + // termination should be called during pending traversal execution + verify(historyCallback, never()).onHistoryCleared(); - Mockito.reset(dispatcher); - // forward to projects list complete dispatcher.fire(); - Mockito.verify(dispatcher, Mockito.times(2)).fire(); - dispatcher.assertIdle(); - assertThat(flow.getHistory().top()).isEqualTo(baker); - assertThat(flow.getHistory().size()).isEqualTo(1); - - // PROJECTS->DETAILS (set) - flow.set(charlie); + verify(historyCallback, times(1)).onHistoryCleared(); +// +// // WELCOME->PROJECTS (replaceTop FORWARD) +// flow.replaceTop(baker, Direction.FORWARD); +// // forward to projects list complete +// dispatcher.fire(); +// dispatcher.assertIdle(); +// assertThat(flow.getHistory().top()).isEqualTo(baker); +// assertThat(flow.getHistory().size()).isEqualTo(1); +// +// // PROJECTS->DETAILS (set) +// flow.set(charlie); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // forward to project details complete +// dispatcher.fire(); +// dispatcher.assertDispatching(able); +// assertThat(flow.getHistory().top()).isEqualTo(charlie); +// assertThat(flow.getHistory().size()).isEqualTo(2); +// +// // WELCOME->PROJECTS (replaceTop FORWARD) +// flow.replaceTop(baker, Direction.FORWARD); +// +// // forward to welcome complete +// dispatcher.fire(); +// dispatcher.assertDispatching(baker); +// assertThat(flow.getHistory().top()).isEqualTo(able); +// assertThat(flow.getHistory().size()).isEqualTo(1); +// +// // WELCOME->PROJECTS (replaceTop FORWARD) +// flow.replaceTop(baker, Direction.FORWARD); +// +// Mockito.reset(dispatcher); +// // forward to projects list complete +// dispatcher.fire(); +// verify(dispatcher, Mockito.times(2)).fire(); +// dispatcher.assertIdle(); +// assertThat(flow.getHistory().top()).isEqualTo(baker); +// assertThat(flow.getHistory().size()).isEqualTo(1); +// +// // PROJECTS->DETAILS (set) +// flow.set(charlie); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // forward to project details complete +// dispatcher.fire(); +// dispatcher.assertDispatching(able); +// assertThat(flow.getHistory().top()).isEqualTo(charlie); +// assertThat(flow.getHistory().size()).isEqualTo(2); +// +// // WELCOME->PROJECTS (replaceTop FORWARD) +// flow.replaceTop(baker, Direction.FORWARD); +// +// Mockito.reset(dispatcher); +// // forward to welcome complete +// dispatcher.fire(); +// verify(dispatcher, Mockito.times(3)).fire(); +// dispatcher.assertDispatching(baker); +// assertThat(flow.getHistory().top()).isEqualTo(able); +// assertThat(flow.getHistory().size()).isEqualTo(1); +// +// // WELCOME->PROJECTS (replaceTop FORWARD) +// flow.replaceTop(baker, Direction.FORWARD); +// +// Mockito.reset(dispatcher); +// // forward to projects list complete +// dispatcher.fire(); +// verify(dispatcher, Mockito.times(2)).fire(); +// dispatcher.assertIdle(); +// assertThat(flow.getHistory().top()).isEqualTo(baker); +// assertThat(flow.getHistory().size()).isEqualTo(1); +// +// // PROJECTS->DETAILS (set) +// flow.set(charlie); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // forward to project details complete +// dispatcher.fire(); +// dispatcher.assertDispatching(able); +// assertThat(flow.getHistory().top()).isEqualTo(charlie); +// assertThat(flow.getHistory().size()).isEqualTo(2); +// +// // WELCOME->PROJECTS (replaceTop FORWARD) +// flow.replaceTop(baker, Direction.FORWARD); +// +// Mockito.reset(dispatcher); +// // forward to welcome complete +// dispatcher.fire(); +// verify(dispatcher, Mockito.times(3)).fire(); +// dispatcher.assertDispatching(baker); +// assertThat(flow.getHistory().top()).isEqualTo(able); +// assertThat(flow.getHistory().size()).isEqualTo(1); +// +// // forward to projects list complete +// dispatcher.fire(); +// dispatcher.assertIdle(); +// assertThat(flow.getHistory().top()).isEqualTo(baker); +// assertThat(flow.getHistory().size()).isEqualTo(1); +// +// // GO BACK +// assertThat(flow.goBack()).isEqualTo(false); +// +// // PROJECTS->DETAILS (set) +// flow.set(charlie); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // DETAILS->WELCOME (replaceHistory FORWARD) +// flow.replaceHistory(able, Direction.FORWARD); +// +// // forward to project details complete +// dispatcher.fire(); +// dispatcher.assertDispatching(able); +// assertThat(flow.getHistory().top()).isEqualTo(charlie); +// assertThat(flow.getHistory().size()).isEqualTo(2); +// +// // GO BACK +// assertThat(flow.goBack()).isEqualTo(true); +// +// // WELCOME->PROJECTS (replaceTop FORWARD) +// flow.replaceTop(baker, Direction.FORWARD); +// +// Mockito.reset(dispatcher); +// // forward to welcome complete +// dispatcher.fire(); +// verify(dispatcher, Mockito.times(3)).fire(); +// dispatcher.assertIdle(); +// assertThat(flow.getHistory().top()).isEqualTo(able); +// assertThat(flow.getHistory().size()).isEqualTo(1); + } - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); +//09-12 08:56:10.633 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) 1 +//09-12 08:56:10.843 7711-7711/com.sdelaysam.testflow D/WTF: forward to projects list complete 1 - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); +//09-12 08:56:11.431 7711-7711/com.sdelaysam.testflow D/WTF: PROJECTS->DETAILS (set) 2 +//09-12 08:56:12.108 7711-7711/com.sdelaysam.testflow D/WTF: DETAILS->WELCOME (replaceHistory FORWARD) 3 - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); +//09-12 08:56:12.158 7711-7711/com.sdelaysam.testflow D/WTF: forward to project details complete 2 +//09-12 08:56:12.675 7711-7711/com.sdelaysam.testflow D/WTF: forward to welcome complete 3 - // forward to project details complete - dispatcher.fire(); - dispatcher.assertDispatching(able); - assertThat(flow.getHistory().top()).isEqualTo(charlie); - assertThat(flow.getHistory().size()).isEqualTo(2); +//09-12 08:56:14.487 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) 4 +//09-12 08:56:14.705 7711-7711/com.sdelaysam.testflow D/WTF: forward to projects list complete 4 - // WELCOME->PROJECTS (replaceTop FORWARD) - flow.replaceTop(baker, Direction.FORWARD); +//09-12 08:56:14.705 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) 5 +//09-12 08:56:14.903 7711-7711/com.sdelaysam.testflow D/WTF: PROJECTS->DETAILS (set) 6 +//09-12 08:56:15.046 7711-7711/com.sdelaysam.testflow D/WTF: GO BACK 7 +//09-12 08:56:15.272 7711-7711/com.sdelaysam.testflow D/WTF: DETAILS->WELCOME (replaceHistory FORWARD) 8 +//09-12 08:56:15.439 7711-7711/com.sdelaysam.testflow D/WTF: DETAILS->WELCOME (replaceHistory FORWARD) 9 - Mockito.reset(dispatcher); - // forward to welcome complete - dispatcher.fire(); - Mockito.verify(dispatcher, Mockito.times(3)).fire(); - dispatcher.assertDispatching(baker); - assertThat(flow.getHistory().top()).isEqualTo(able); - assertThat(flow.getHistory().size()).isEqualTo(1); +//09-12 08:56:15.621 7711-7711/com.sdelaysam.testflow D/WTF: forward to project details complete 5(auto), 6 - // forward to projects list complete - dispatcher.fire(); - dispatcher.assertIdle(); - assertThat(flow.getHistory().top()).isEqualTo(baker); - assertThat(flow.getHistory().size()).isEqualTo(1); +//09-12 08:56:15.690 7711-7711/com.sdelaysam.testflow D/WTF: DETAILS->WELCOME (replaceHistory FORWARD) 10 +//09-12 08:56:15.855 7711-7711/com.sdelaysam.testflow D/WTF: DETAILS->WELCOME (replaceHistory FORWARD) 11 +//09-12 08:56:16.055 7711-7711/com.sdelaysam.testflow D/WTF: DETAILS->WELCOME (replaceHistory FORWARD) 12 +//09-12 08:56:16.206 7711-7711/com.sdelaysam.testflow D/WTF: GO BACK 13 +//09-12 08:56:16.306 7711-7711/com.sdelaysam.testflow D/WTF: DETAILS->WELCOME (replaceHistory FORWARD) 14 - // GO BACK - assertThat(flow.goBack()).isEqualTo(false); +//09-12 08:56:16.340 7711-7711/com.sdelaysam.testflow D/WTF: back to projects list complete 7 - // PROJECTS->DETAILS (set) - flow.set(charlie); +//09-12 08:56:16.506 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) 15 +//09-12 08:56:16.674 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) 16 - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); +//09-12 08:56:16.856 7711-7711/com.sdelaysam.testflow D/WTF: forward to welcome complete 8, 9 - 12 - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); +//09-12 08:56:16.874 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) 17 +//09-12 08:56:17.029 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) 18 +//09-12 08:56:17.112 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) ... +//09-12 08:56:17.238 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:17.379 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:17.545 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:17.720 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:17.837 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:18.237 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:18.478 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:18.695 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:19.154 7711-7711/com.sdelaysam.testflow D/WTF: GO BACK +//09-12 08:56:19.803 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:20.094 7711-7711/com.sdelaysam.testflow D/WTF: WELCOME->PROJECTS (replaceTop FORWARD) +//09-12 08:56:20.629 7711-7711/com.sdelaysam.testflow D/WTF: GO BACK - // DETAILS->WELCOME (replaceHistory FORWARD) - flow.replaceHistory(able, Direction.FORWARD); - // forward to project details complete - dispatcher.fire(); - dispatcher.assertDispatching(able); - assertThat(flow.getHistory().top()).isEqualTo(charlie); - assertThat(flow.getHistory().size()).isEqualTo(2); - - // GO BACK - assertThat(flow.goBack()).isEqualTo(true); - - // WELCOME->PROJECTS (replaceTop FORWARD) - flow.replaceTop(baker, Direction.FORWARD); - Mockito.reset(dispatcher); - // forward to welcome complete - dispatcher.fire(); - Mockito.verify(dispatcher, Mockito.times(3)).fire(); - dispatcher.assertIdle(); - assertThat(flow.getHistory().top()).isEqualTo(able); - assertThat(flow.getHistory().size()).isEqualTo(1); - } } diff --git a/flow/src/test/java/flow/ReentranceTest.java b/flow/src/test/java/flow/ReentranceTest.java index e6d113c..32d4297 100644 --- a/flow/src/test/java/flow/ReentranceTest.java +++ b/flow/src/test/java/flow/ReentranceTest.java @@ -29,6 +29,8 @@ import static flow.Direction.FORWARD; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.MockitoAnnotations.initMocks; public class ReentranceTest { @@ -37,6 +39,7 @@ public class ReentranceTest { Flow flow; History lastStack; TraversalCallback lastCallback; + @Mock HistoryCallback historyCallback; @Before public void setUp() { initMocks(this); @@ -125,10 +128,12 @@ public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback ca lastStack = traversal.destination; } }); + flow.setHistoryCallback(historyCallback); flow.set(new Detail()); flow.set(new Error()); - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); while (!callbacks.isEmpty()) { callbacks.poll().onTraversalCompleted(); @@ -148,11 +153,13 @@ public void dispatch(@NonNull Traversal traversal, @NonNull TraversalCallback ca lastStack = traversal.destination; } }); + flow.setHistoryCallback(historyCallback); flow.set(new Detail()); for (int i = 0; i < 20; i++) { - assertThat(flow.goBack()).isTrue(); + flow.goBack(); + verify(historyCallback, never()).onHistoryCleared(); } int callbackCount = 0;