public class MyApp extends Application {

    private static Fluxxan<AppState> sFluxxan = null;
    private PersistenceMiddleware mPersistenceMiddleware = null;

    @Override
    public void onCreate() {
        super.onCreate();
        initializeFluxxan();
    }

    private void initializeFluxxan() {
        sFluxxan = new MyFluxxan(ImmutableAppState.builder().build());
        sFluxxan.registerReducer(new AppStateReducer());

        DataManager dm = new DataManager(getBaseContext());

        Middleware restMiddleware = new RestMiddleware(getBaseContext(), dm, new FileDownloader());
        sFluxxan.getDispatcher().registerMiddleware(restMiddleware);

        mPersistenceMiddleware = new PersistenceMiddleware(dm);
        sFluxxan.getDispatcher().registerMiddleware(mPersistenceMiddleware);
        sFluxxan.addListener(mPersistenceMiddleware);
        sFluxxan.start();
    }

    public void onTerminate() {
        sFluxxan.removeListener(mPersistenceMiddleware);
        sFluxxan.stop();
        super.onTerminate();
    }

    public static Fluxxan<AppState> getFluxxan() {
        return sFluxxan;
    }
} public final class GifListActionCreator extends BaseActionCreator {

    public static final String GIF_LIST_UPDATED = "GIF_LIST_UPDATED";
    public static final String GIF_LIST_FETCHING = "GIF_LIST_FETCHING";

    private static GifListActionCreator sInstance;

    private GifListActionCreator() {
        MyApp.getFluxxan().inject(this);
    }

    public static GifListActionCreator getInstance() {
        if (sInstance == null) {
            sInstance = new GifListActionCreator();
        }
        return sInstance;
    }

    public void fetchGifs() {
        dispatch(new Action(GIF_LIST_FETCHING));
    }
} public final class PayloadParams {

    public static final String PARAM_UUID = "PARAM_UUID";
    public static final String PARAM_PATH = "PARAM_PATH";
    public static final String PARAM_GIFS = "PARAM_GIFS";
    public static final String PARAM_HAS_MORE = "PARAM_HAS_MORE";

    private PayloadParams() {
    }
} @SuppressWarnings("PMD.BooleanGetMethodName")
@Value.Immutable
@JsonSerialize(as = ImmutableAppState.class)
@JsonDeserialize(as = ImmutableAppState.class)
public abstract class AppState {

    @Value.Default
    public Map<String, Gif> getGifs() {
        return new LinkedHashMap<>();
    }

    @Value.Default
    public boolean getHasMoreGifs() {
        return true;
    }

    public String toJson() throws JsonProcessingException {
        return new ObjectMapper().writeValueAsString(this);
    }

    public static AppState fromJson(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new GuavaModule());
        return mapper.readValue(json, AppState.class);
    }

} @SuppressWarnings("PMD.BooleanGetMethodName")
@Value.Immutable
@JsonSerialize(as = ImmutableGif.class)
@JsonDeserialize(as = ImmutableGif.class)
public abstract class Gif {

    public enum Status {
        PAUSED, LOOPING, DOWNLOADING, DOWNLOADED, NOT_DOWNLOADED, DOWNLOAD_FAILED
    }

    //abstract method are immutable by default
    public abstract String getUuid();

    @Value.Default
    public String getPath() {
        return "";
    }

    public abstract String getUrl();
    
    public abstract String getTitle();

    @Value.Default
    public boolean getWatched() {
        return false;
    }

    @Value.Default
    public Status getStatus() {
        return Status.NOT_DOWNLOADED;
    }

    public String toJson() throws JsonProcessingException {
        return new ObjectMapper().writeValueAsString(this);
    }

    public static Gif fromJson(String json) throws IOException {
        return new ObjectMapper().readValue(json, Gif.class);
    }

} public class ReduxBaseTest {
    @Rule
    public Timeout mGlobalTimeout = new Timeout(1, TimeUnit.MINUTES);

    protected TestHelper mHelper = null;

    @Before
    public void setup() {
        if (mHelper != null) {
            mHelper.getFluxxan().registerReducer(new FakeReducer());
            mHelper.activateStateListener();
        } else {
            informTestHelperNotInitialized();
        }
    }

    @After
    public void cleanup() {
        if (mHelper != null) {
            mHelper.deactivateStateListener();
            mHelper.getFluxxan().unregisterReducer(FakeReducer.class);
        } else {
            informTestHelperNotInitialized();
        }
    }

    private void informTestHelperNotInitialized() {
        Log.e(getClass().getSimpleName(),
                "TestHelper not initialized: set super.mHelper with properly Fluxxan instance");
    }
} public class TestHelper {
    public static final String STRING_UNIQUE = UUID.randomUUID().toString();
    public static final String STRING_UNIQUE_2 = UUID.randomUUID().toString();
    public static final String STRING_UNIQUE_3 = UUID.randomUUID().toString();
    public static final String DEFAULT_UUID = "default-uuid";
    public static final Float FLOAT_RANDOM = new Random().nextFloat();
    public static final float DELTA = 0.00001f;

    private final Object mLock = new Object();

    private Fluxxan mFluxxan;
    private boolean mStateChanged = false;

    private StateListener mStateListener = new StateListener() {
        @Override
        public boolean hasStateChanged(Object newState, Object oldState) {
            setStateChanged(true);
            return newState != oldState;
        }

        @Override
        public void onStateChanged(Object o) {
        }
    };

    public TestHelper(Fluxxan fluxxan) {
        mFluxxan = fluxxan;
    }

    public void activateStateListener() {
        getFluxxan().getDispatcher().addListener(mStateListener);
    }

    public void deactivateStateListener() {
        getFluxxan().getDispatcher().removeListener(mStateListener);
    }

    private boolean isStateChanged() {
        synchronized (mLock) {
            return mStateChanged;
        }
    }

    private void setStateChanged(boolean value) {
        synchronized (mLock) {
            mStateChanged = value;
        }
    }

    public Fluxxan<AppState> getFluxxan() {
        return mFluxxan;
    }

    public void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            Log.e(getClass().getSimpleName(), "", e);
        }
    }

    // Helpers methods to dry up unit tests
    public void dispatchAction(Action action) {
        getFluxxan().getDispatcher().dispatch(action);
        // Since the dispatcher send actions in background we need wait the response arrives
        while (!isStateChanged() || getFluxxan().getDispatcher().isDispatching()) {
            sleep(5);
        }
        setStateChanged(false);
    }

    public void dispatchFakeAppState(AppState state) {
        dispatchAction(new Action(FakeReducer.FAKE_REDUCE_ACTION, state));
    }

    public Gif firstAppStateGif() {
        Map<String, Gif> gifs = getFluxxan().getState().getGifs();
        if (gifs.size() <= 0) return null;
        return gifs.values().iterator().next();
    }

    public static AppState buildAppState(Gif gif) {
        return ImmutableAppState.builder().putGifs(gif.getUuid(), gif).build();
    }

    public static AppState buildAppState(Map<String, Gif> gifs) {
        return ImmutableAppState.builder().gifs(gifs).build();
    }

    public static Gif buildGif() {
        return gifBuilderWithDefault().build();
    }

    public static Gif buildGif(Gif.Status status) {
        return buildGif(status, DEFAULT_UUID);
    }

    public static Gif buildGif(Gif.Status status, String uuid) {
        return gifBuilderWithDefault()
                .uuid(uuid)
                .status(status)
                .build();
    }

    public static ImmutableGif.Builder gifBuilderWithEmpty() {
        return ImmutableGif.builder()
                .uuid(UUID.randomUUID().toString())
                .title("")
                .url("");
    }

    public static ImmutableGif.Builder gifBuilderWithDefault() {
        return ImmutableGif.builder()
                .uuid(DEFAULT_UUID)
                .title("Gif")
                .url("https://media.giphy.com/media/l0HlE56oAxpngfnWM/giphy.gif")
                .status(Gif.Status.NOT_DOWNLOADED);

    }

    public static Map<String, Gif> buildFiveGifs() {
        String[] titles = {"Gif 1", "Gif 2", "Gif 3", "Gif 4", "Gif 5" };
        String[] urls = {
                "https://media.giphy.com/media/l0HlE56oAxpngfnWM/giphy.gif",
                "http://inspirandoideias.com.br/blog/wp-content/uploads/2015/03/"
                        + "b3368a682fc5ff891e41baad2731f4b6.gif",
                "https://media.giphy.com/media/9fbYYzdf6BbQA/giphy.gif",
                "https://media.giphy.com/media/l2YWl1oQlNvthGWrK/giphy.gif",
                "https://media.giphy.com/media/3oriNQHSU0bVcFW5sA/giphy.gif"
        };

        Map<String, Gif> gifs = new LinkedHashMap<>();
        for (int i = 0; i < titles.length; i++) {
            Gif gif = ImmutableGif.builder().uuid(UUID.randomUUID().toString())
                    .title(titles[i])
                    .url(urls[i])
                    .build();
            gifs.put(gif.getUuid(), gif);
        }

        return gifs;
    }

    public DataManager mockDataManagerFetch(Map<String, Gif> expectedGifs,
                                            boolean expectedHasMore) {
        DataManager dataManager = mock(DataManager.class);
        ArgumentCaptor<DataManager.GifListLoadListener> listenerCaptor
                = ArgumentCaptor.forClass(DataManager.GifListLoadListener.class);
        doAnswer((invocationOnMock) -> {
            new Thread(() -> {
                // The fluxxan don't let we dispatch when it's dispatching
                while (mFluxxan.getDispatcher().isDispatching()) {
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        Log.e("Test", e.getMessage(), e);
                    }
                }
                listenerCaptor.getValue().onLoaded(expectedGifs, expectedHasMore);
            }).start();
            return null;
        }).when(dataManager).fetchGifs(listenerCaptor.capture());
        return dataManager;
    }

    private void clearRestMiddleware() {
        getFluxxan().getDispatcher().unregisterMiddleware(RestMiddleware.class);
    }

    private void clearPersistenceMiddleware() {
        PersistenceMiddleware persistenceMiddleware = (PersistenceMiddleware) getFluxxan()
                .getDispatcher().unregisterMiddleware(PersistenceMiddleware.class);
        getFluxxan().getDispatcher().removeListener(persistenceMiddleware);
    }

    public void clearMiddlewares() {
        clearRestMiddleware();
        clearPersistenceMiddleware();
    }

    public void replaceRestMiddleware(DataManager dm, Context context) {
        clearRestMiddleware();

        RestMiddleware restMiddleware = new RestMiddleware(context, dm, new FileDownloader());
        getFluxxan().getDispatcher().registerMiddleware(restMiddleware);
    }

    public void replacePersistenceMiddleware(DataManager dm) {
        clearPersistenceMiddleware();

        PersistenceMiddleware persistenceMiddleware = new PersistenceMiddleware(dm);
        getFluxxan().getDispatcher().registerMiddleware(persistenceMiddleware);
        getFluxxan().addListener(persistenceMiddleware);
    }

    public void replaceMiddlewares(DataManager dm, Context context) {
        replaceRestMiddleware(dm, context);
        replacePersistenceMiddleware(dm);
    }
} public static final String GIF_DOWNLOAD_FAILURE = "GIF_DOWNLOAD_FAILURE"; public static final String GIF_DOWNLOAD_START = "GIF_DOWNLOAD_START"; public static final String GIF_DOWNLOAD_SUCCESS = "GIF_DOWNLOAD_SUCCESS"; public static final String GIF_PAUSE = "GIF_PAUSE"; public static final String GIF_PLAY = "GIF_PLAY"; public static final String DEFAULT_UUID = "default-uuid"; public static AppState buildAppState(Gif gif) {
    return ImmutableAppState.builder().putGifs(gif.getUuid(), gif).build();
} public static Map<String, Gif> buildFiveGifs() {
    String[] titles = {"Gif 1", "Gif 2", "Gif 3", "Gif 4", "Gif 5" };
    String[] urls = {
            "https://media.giphy.com/media/l0HlE56oAxpngfnWM/giphy.gif",
            "http://inspirandoideias.com.br/blog/wp-content/uploads/2015/03/"
                    + "b3368a682fc5ff891e41baad2731f4b6.gif",
            "https://media.giphy.com/media/9fbYYzdf6BbQA/giphy.gif",
            "https://media.giphy.com/media/l2YWl1oQlNvthGWrK/giphy.gif",
            "https://media.giphy.com/media/3oriNQHSU0bVcFW5sA/giphy.gif"
    };

    Map<String, Gif> gifs = new LinkedHashMap<>();
    for (int i = 0; i < titles.length; i++) {
        Gif gif = ImmutableGif.builder().uuid(UUID.randomUUID().toString())
                .title(titles[i])
                .url(urls[i])
                .build();
        gifs.put(gif.getUuid(), gif);
    }

    return gifs;
} public static Gif buildGif() {
    return gifBuilderWithDefault().build();
} public static ImmutableGif.Builder gifBuilderWithDefault() {
    return ImmutableGif.builder()
            .uuid(DEFAULT_UUID)
            .title("Gif")
            .url("https://media.giphy.com/media/l0HlE56oAxpngfnWM/giphy.gif")
            .status(Gif.Status.NOT_DOWNLOADED);

}
import com.umaplay.fluxxan.Action;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import br.com.catbag.gifreduxsample.BuildConfig;
import br.com.catbag.gifreduxsample.MyApp;
import br.com.catbag.gifreduxsample.actions.GifListActionCreator;
import br.com.catbag.gifreduxsample.actions.PayloadParams;
import br.com.catbag.gifreduxsample.models.AppState;
import br.com.catbag.gifreduxsample.models.Gif;
import br.com.catbag.gifreduxsample.models.ImmutableAppState;
import shared.ReduxBaseTest;
import shared.TestHelper;
import static br.com.catbag.gifreduxsample.actions.GifActionCreator.GIF_DOWNLOAD_FAILURE;
import static br.com.catbag.gifreduxsample.actions.GifActionCreator.GIF_DOWNLOAD_START;
import static br.com.catbag.gifreduxsample.actions.GifActionCreator.GIF_DOWNLOAD_SUCCESS;
import static br.com.catbag.gifreduxsample.actions.GifActionCreator.GIF_PAUSE;
import static br.com.catbag.gifreduxsample.actions.GifActionCreator.GIF_PLAY;
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static shared.TestHelper.DEFAULT_UUID;
import static shared.TestHelper.buildAppState;
import static shared.TestHelper.buildFiveGifs;
import static shared.TestHelper.buildGif;
import static shared.TestHelper.gifBuilderWithDefault;
package br.com.catbag.gifreduxsample.reducers.reducers;






/**
 Copyright 26/10/2016
 Felipe Piñeiro (fpbitencourt@gmail.com),
 Nilton Vasques (nilton.vasques@gmail.com) and
 Raul Abreu (raulccabreu@gmail.com)

 Be free to ask for help, email us!

 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 in compliance with the License. You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software distributed under the License
 is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 implied. See the License for the specific language governing permissions and limitations under
 the License.
 **/

// Roboeletric still not supports API 24 stuffs
@Config(sdk = 23, constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
public class GifListReducerTest extends ReduxBaseTest {

    public GifListReducerTest() {
        mHelper = new TestHelper(MyApp.getFluxxan());
        mHelper.clearMiddlewares();
    }

    @Test
    public void whenSendDownloadStartedAction() throws Exception {
        assertTransition(Gif.Status.NOT_DOWNLOADED, Gif.Status.DOWNLOADING, GIF_DOWNLOAD_START,
                DEFAULT_UUID);
    }

    @Test
    public void whenSendLoadListAction() throws Exception {
        Map<String, Gif> fakeGifs = buildFiveGifs();

        Map<String, Object> params = new HashMap<>();
        params.put(PayloadParams.PARAM_GIFS, fakeGifs);
        params.put(PayloadParams.PARAM_HAS_MORE, false);
        mHelper.dispatchAction(new Action(GifListActionCreator.GIF_LIST_UPDATED, params));

        Map<String, Gif> gifsStates = mHelper.getFluxxan().getState().getGifs();
        assertEquals(fakeGifs.size(), gifsStates.size());
        assertFalse(mHelper.getFluxxan().getState().getHasMoreGifs());

        for (Gif expectedGif : fakeGifs.values()) {
            assertEquals(expectedGif, gifsStates.get(expectedGif.getUuid()));
        }
    }

    @Test
    public void whenSendLoadListWithANonEmptyListAction() throws Exception {
        Map<String, Gif> fakeGifs = buildFiveGifs();

        Map<String, Object> params = new HashMap<>();
        params.put(PayloadParams.PARAM_GIFS, fakeGifs);
        params.put(PayloadParams.PARAM_HAS_MORE, true);
        mHelper.dispatchAction(new Action(GifListActionCreator.GIF_LIST_UPDATED, params));

        Map<String, Gif> fakeGifs2 = buildFiveGifs();
        params = new HashMap<>();
        params.put(PayloadParams.PARAM_GIFS, fakeGifs2);
        params.put(PayloadParams.PARAM_HAS_MORE, false);
        mHelper.dispatchAction(new Action(GifListActionCreator.GIF_LIST_UPDATED, params));

        Map<String, Gif> gifsStates = mHelper.getFluxxan().getState().getGifs();
        assertEquals(fakeGifs.size() + fakeGifs2.size(), gifsStates.size());
        assertFalse(mHelper.getFluxxan().getState().getHasMoreGifs());

        for (Gif expectedGif : fakeGifs.values()) {
            assertEquals(expectedGif, gifsStates.get(expectedGif.getUuid()));
        }
        for (Gif expectedGif : fakeGifs2.values()) {
            assertEquals(expectedGif, gifsStates.get(expectedGif.getUuid()));
        }
    }

    @Test
    public void whenSendDownloadSuccessAction() throws Exception {
        String expectedPath = "/test/image.gif";
        Map<String, Object> params = new HashMap<>();
        params.put(PayloadParams.PARAM_PATH, expectedPath);
        params.put(PayloadParams.PARAM_UUID, DEFAULT_UUID);

        assertTransition(Gif.Status.DOWNLOADING, Gif.Status.DOWNLOADED, GIF_DOWNLOAD_SUCCESS,