public class BehavioursCoordinator<T> implements FlowableTransformer<T, T> {

    private AssignEmptyState<T> dealWithEmptyState;
    private AssignErrorState<T> assignErrorState;
    private LoadingCoordination<T> loadingCoordinator;
    private NetworkingErrorFeedback<T> networkingErrorFeedback;
    private RefreshToogle<T> refreshToogle;

    public BehavioursCoordinator(AssignEmptyState<T> dealWithEmptyState,
                                 AssignErrorState<T> assignErrorState,
                                 LoadingCoordination<T> loadingCoordinator,
                                 NetworkingErrorFeedback<T> networkingErrorFeedback,
                                 RefreshToogle<T> refreshToogle) {

        this.dealWithEmptyState = dealWithEmptyState;
        this.assignErrorState = assignErrorState;
        this.loadingCoordinator = loadingCoordinator;
        this.networkingErrorFeedback = networkingErrorFeedback;
        this.refreshToogle = refreshToogle;
    }

    @Override public Publisher<T> apply(Flowable<T> upstream) {
        return upstream
                .compose(dealWithEmptyState)
                .compose(assignErrorState)
                .compose(loadingCoordinator)
                .compose(refreshToogle)
                .compose(networkingErrorFeedback);
    }
} public class AssignEmptyState<T> implements FlowableTransformer<T, T> {

    private EmptyStateView view;
    private Scheduler uiScheduler;

    public AssignEmptyState(EmptyStateView view, Scheduler uiScheduler) {
        this.view = view;
        this.uiScheduler = uiScheduler;
    }

    @Override public Publisher<T> apply(Flowable<T> upstream) {

        HideAtStartShowAtError<T> delegate = new HideAtStartShowAtError<>(
                view.hideEmptyState(),
                view.showEmptyState(),
                error -> error instanceof ContentNotFoundError,
                uiScheduler
        );

        return upstream.compose(delegate);
    }

} public class AssignErrorState<T> implements FlowableTransformer<T, T> {

    private ErrorStateView view;
    private Scheduler uiScheduler;

    public AssignErrorState(ErrorStateView view, Scheduler uiScheduler) {
        this.view = view;
        this.uiScheduler = uiScheduler;
    }

    @Override public Publisher<T> apply(Flowable<T> upstream) {
        HideAtStartShowAtError<T> delegate = new HideAtStartShowAtError<>(
                view.hideErrorState(),
                view.showErrorState(),
                error -> !(error instanceof ContentNotFoundError),
                uiScheduler
        );

        return upstream.compose(delegate);
    }

} public class LifecycleStrategist {

    private DisposeStrategy strategy;

    public LifecycleStrategist(LifecycleOwner owner, DisposeStrategy strategy) {
        this.strategy = strategy;
        owner.getLifecycle().addObserver(strategy);
    }

    public void applyStrategy(Disposable toDispose) {
        strategy.addDisposable(toDispose);
    }

} public class LoadingCoordination<T> implements FlowableTransformer<T, T> {

    private LoadingView view;
    private Scheduler uiScheduler;

    public LoadingCoordination(LoadingView view,
                               Scheduler uiScheduler) {
        this.view = view;
        this.uiScheduler = uiScheduler;
    }

    @Override public Publisher<T> apply(Flowable<T> upstream) {

        ShowAtStartHideWhenDone<T> delegate = new ShowAtStartHideWhenDone<>(
                view.showLoading(),
                view.hideLoading(),
                uiScheduler
        );

        return upstream.compose(delegate);
    }

} public class NetworkingError extends RuntimeException {

    public NetworkingError(String message) {
        super(message);
    }

} public class NetworkingErrorFeedback<T> implements FlowableTransformer<T, T> {

    private NetworkingErrorView view;
    private Scheduler uiScheduler;

    public NetworkingErrorFeedback(NetworkingErrorView view, Scheduler uiScheduler) {
        this.view = view;
        this.uiScheduler = uiScheduler;
    }

    @Override public Publisher<T> apply(Flowable<T> upstream) {
        return upstream.doOnError(this::handleIfNetworkingError);
    }

    private Publisher<T> handleIfNetworkingError(Throwable throwable) {

        if (throwable instanceof NetworkingError) {
            Completable.fromAction(view.reportNetworkingError())
                    .observeOn(uiScheduler)
                    .subscribe();
        }

        return Flowable.error(throwable);
    }

} public class RefreshToogle<T> implements FlowableTransformer<T, T> {

    private final ToogleRefreshView view;
    private Scheduler targetScheduler;

    public RefreshToogle(ToogleRefreshView view, Scheduler targetScheduler) {
        this.view = view;
        this.targetScheduler = targetScheduler;
    }

    @Override public Publisher<T> apply(Flowable<T> upstream) {
        return upstream
                .doOnSubscribe(subscription -> fireAction(view.disableRefresh()))
                .doOnError(this::enableForSpecialErrorCase)
                .doOnComplete(() -> fireAction(view.enableRefresh()));
    }

    private void enableForSpecialErrorCase(Throwable throwable) {
        if (!(throwable instanceof UnexpectedResponseError)) fireAction(view.enableRefresh());
    }

    private void fireAction(Action toPerform) {
        Completable.fromAction(toPerform)
                .subscribeOn(targetScheduler)
                .subscribe();
    }

} public class FactAboutNumber {

    public String number;
    public String fact;

    private FactAboutNumber(String number, String fact) {
        this.number = number;
        this.fact = fact;
    }

    public static FactAboutNumber of(String number, String fact) {
        return new FactAboutNumber(number, fact);
    }
} public interface GetRandomFacts {

    Flowable<FactAboutNumber> fetchTrivia();

} public class ContentNotFoundError extends RuntimeException {

    @Override public String getMessage() {
        return "No content available";
    }

} public class UnexpectedResponseError extends RuntimeException {

    public UnexpectedResponseError(String message) {
        super(message);
    }

} public class FactsPresenter {

    private GetRandomFacts usecase;
    private DisplayFactsView view;
    private BehavioursCoordinator<FactAboutNumber> coordinator;
    private LifecycleStrategist strategist;
    private DomainToViewModel<FactAboutNumber, FactViewModel> mapper;

    public FactsPresenter(GetRandomFacts usecase,
                          DisplayFactsView view,
                          BehavioursCoordinator<FactAboutNumber> coordinator,
                          LifecycleStrategist strategist,
                          DomainToViewModel<FactAboutNumber, FactViewModel> mapper) {

        this.usecase = usecase;
        this.view = view;
        this.coordinator = coordinator;
        this.strategist = strategist;
        this.mapper = mapper;
    }

    public void fetchRandomFacts() {
        Flowable<FactViewModel> dataFlow =
                usecase.fetchTrivia()
                        .compose(coordinator)
                        .map(fact -> mapper.translate(fact));

        Disposable toDispose = view.subscribeInto(dataFlow);
        strategist.applyStrategy(toDispose);
    }

} public interface FactViewModel {

    int TYPE_TWO_LABELS = 0;
    int TYPE_SINGLE_LABEL = 1;

    CharSequence formattedFact();

    int viewType();

} public class NumberAndFact implements FactViewModel {

    private String number;
    private String fact;

    public NumberAndFact(String number, String fact) {
        this.number = number;
        this.fact = fact;
    }
    
    @Override public CharSequence formattedFact() {
        return fact;
    }

    @Override public int viewType() {
        return TYPE_TWO_LABELS;
    }

    public String relatedNumber() {
        return number;
    }

} public class BehavioursRobot {

    private Object target;

    private BehavioursRobot(Object target) {
        this.target = target;
    }

    public static BehavioursRobot with(Object target) {
        return new BehavioursRobot(target);
    }

    public BehavioursRobot shouldShowErrorState() throws Exception {
        checkErrorStateView();
        ErrorStateView view = (ErrorStateView) target;
        verify(view.showErrorState(), oneTimeOnly()).run();
        return this;
    }

    public BehavioursRobot shouldNotShowErrorState() throws Exception {
        checkErrorStateView();
        ErrorStateView view = (ErrorStateView) target;
        verify(view.showErrorState(), never()).run();
        return this;
    }

    public BehavioursRobot shouldShowEmptyState() throws Exception {
        checkEmptyStateView();
        EmptyStateView view = (EmptyStateView) target;
        verify(view.showEmptyState(), oneTimeOnly()).run();
        return this;
    }

    public BehavioursRobot shouldNotShowEmptyState() throws Exception {
        checkEmptyStateView();
        EmptyStateView view = (EmptyStateView) target;
        verify(view.showEmptyState(), never()).run();
        return this;
    }

    public BehavioursRobot shouldReportNetworkingError() throws Exception {
        checkNetworkingErrorView();
        NetworkingErrorView view = (NetworkingErrorView) target;
        verify(view.reportNetworkingError(), oneTimeOnly()).run();
        return this;
    }

    public BehavioursRobot shouldNotReportNetworkingError() throws Exception {
        checkNetworkingErrorView();
        NetworkingErrorView view = (NetworkingErrorView) target;
        verify(view.reportNetworkingError(), never()).run();
        return this;
    }

    public BehavioursRobot showLoadingFirstHideLoadingAfter() throws Exception {
        checkLoadingView();
        LoadingView view = (LoadingView) target;
        verifyInOrderShowAndHideLoading(view);
        return this;
    }

    public BehavioursRobot disableRefreshFirstAndEnableAfter() throws Exception {
        checkToogleRefreshView();
        ToogleRefreshView view = (ToogleRefreshView) target;
        verifyDisableEnableRefresh(view);
        return this;
    }

    public BehavioursRobot disableRefreshFirstAndNotEnableAfter() throws Exception {
        checkToogleRefreshView();
        ToogleRefreshView view = (ToogleRefreshView) target;
        verifyDisableAndNotEnableRefresh(view);
        return this;
    }

    private void verifyInOrderShowAndHideLoading(LoadingView view) throws Exception {
        InOrder inOrder = inOrder(view.showLoading(), view.hideLoading());
        inOrder.verify(view.showLoading(), oneTimeOnly()).run();
        inOrder.verify(view.hideLoading(), oneTimeOnly()).run();
    }

    private void verifyDisableEnableRefresh(ToogleRefreshView view) throws Exception {
        InOrder inOrder = inOrder(view.enableRefresh(), view.disableRefresh());
        inOrder.verify(view.disableRefresh(), oneTimeOnly()).run();
        inOrder.verify(view.enableRefresh(), oneTimeOnly()).run();
    }

    private void verifyDisableAndNotEnableRefresh(ToogleRefreshView view) throws Exception {
        InOrder inOrder = inOrder(view.enableRefresh(), view.disableRefresh());
        inOrder.verify(view.disableRefresh(), oneTimeOnly()).run();
        inOrder.verify(view.enableRefresh(), never()).run();
    }

    private void checkEmptyStateView() {
        if (!(target instanceof EmptyStateView))
            throw new IllegalStateException("Target view not instance of EmptyStateView");
    }

    private void checkErrorStateView() {
        if (!(target instanceof ErrorStateView))
            throw new IllegalStateException("Target view not instance of ErrorStateView");
    }

    private void checkLoadingView() {
        if (!(target instanceof LoadingView))
            throw new IllegalStateException("Target view not instance of LoadingView");
    }

    private void checkNetworkingErrorView() {
        if (!(target instanceof NetworkingErrorView))
            throw new IllegalStateException("Target view not instance of NetworkingErroView");
    }

    private void checkToogleRefreshView() {
        if (!(target instanceof ToogleRefreshView))
            throw new IllegalStateException("Target view not instance of ToogleRefreshView");
    }


} public class DataFlowWatcher {

    private Consumer<?> onNext;
    private Consumer<Throwable> onError;
    private Action onCompleted;

    private DataFlowWatcher(Consumer<?> onNext,
                            Consumer<Throwable> onError,
                            Action onCompleted) {

        this.onNext = onNext;
        this.onError = onError;
        this.onCompleted = onCompleted;
    }

    public static DataFlowWatcher with(Consumer<?> onNext,
                                       Consumer<Throwable> onError,
                                       Action onCompleted) {

        return new DataFlowWatcher(onNext, onError, onCompleted);
    }

    public void shouldReceiveItems(int quantity) throws Exception {

        if (quantity == 0) {
            throw new IllegalArgumentException("Should receive at least one item");
        }

        verify(onNext, atMost(quantity)).accept(any());
        verify(onCompleted).run();
        verifyZeroInteractions(onError);
    }

    public void shouldFinishWithError() throws Exception {
        verify(onError, oneTimeOnly()).accept(any());
        verifyZeroInteractions(onNext);
        verifyZeroInteractions(onError);
    }

    public void shouldNotReceiveAnyData() throws Exception {
        verify(onCompleted, oneTimeOnly()).run();
        verifyZeroInteractions(onNext);
        verifyZeroInteractions(onError);
    }
} public static VerificationMode oneTimeOnly() {
    return VerificationModeFactory.times(1);
}
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import br.ufs.demos.rxmvp.playground.core.behaviours.BehavioursCoordinator;
import br.ufs.demos.rxmvp.playground.core.behaviours.emptystate.AssignEmptyState;
import br.ufs.demos.rxmvp.playground.core.behaviours.errorstate.AssignErrorState;
import br.ufs.demos.rxmvp.playground.core.lifecycles.LifecycleStrategist;
import br.ufs.demos.rxmvp.playground.core.behaviours.loadingcontent.LoadingCoordination;
import br.ufs.demos.rxmvp.playground.core.behaviours.errors.NetworkingError;
import br.ufs.demos.rxmvp.playground.core.behaviours.networking.NetworkingErrorFeedback;
import br.ufs.demos.rxmvp.playground.core.behaviours.tooglerefresh.RefreshToogle;
import br.ufs.demos.rxmvp.playground.trivia.domain.FactAboutNumber;
import br.ufs.demos.rxmvp.playground.trivia.domain.GetRandomFacts;
import br.ufs.demos.rxmvp.playground.core.behaviours.errors.ContentNotFoundError;
import br.ufs.demos.rxmvp.playground.core.behaviours.errors.UnexpectedResponseError;
import br.ufs.demos.rxmvp.playground.trivia.presentation.FactsPresenter;
import br.ufs.demos.rxmvp.playground.trivia.presentation.models.FactViewModel;
import br.ufs.demos.rxmvp.playground.trivia.presentation.models.NumberAndFact;
import br.ufs.demos.rxmvp.playground.util.BehavioursRobot;
import br.ufs.demos.rxmvp.playground.util.DataFlowWatcher;
import io.reactivex.Flowable;
import io.reactivex.Scheduler;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import static br.ufs.demos.rxmvp.playground.util.MockitoHelpers.oneTimeOnly;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
package br.ufs.demos.rxmvp.playground.trivia;




/**
 * Created by bira on 6/30/17.
 */

public class FactsPresenterTests {

    FakeDisplayFactsView view;
    FactsPresenter presenter;

    @Mock LifecycleStrategist strategist;
    @Mock GetRandomFacts usecase;
    @Mock Consumer<FactViewModel> onNext;
    @Mock Consumer<Throwable> onError;
    @Mock Action onCompleted;

    @Before public void beforeEachTest() {
        MockitoAnnotations.initMocks(this);
        Scheduler uiScheduler = Schedulers.trampoline();

        view = new FakeDisplayFactsView(onNext, onError, onCompleted);

        BehavioursCoordinator<FactAboutNumber> coordinator = new BehavioursCoordinator<>(
                new AssignEmptyState<>(view, uiScheduler),
                new AssignErrorState<>(view, uiScheduler),
                new LoadingCoordination<>(view, uiScheduler),
                new NetworkingErrorFeedback<>(view, uiScheduler),
                new RefreshToogle<>(view, uiScheduler)
        );

        presenter = new FactsPresenter(
                usecase,
                view,
                coordinator,
                strategist,
                domainData -> new NumberAndFact(domainData.number, domainData.fact)
        );
    }

    @Test public void shouldPresent_AvailableData_IntoView() throws Exception {

        Flowable<FactAboutNumber> data = Flowable.just(
                FactAboutNumber.of("1", "1 is the first"),
                FactAboutNumber.of("2", "2 is the second")
        );

        when(usecase.fetchTrivia()).thenReturn(data);

        presenter.fetchRandomFacts();

        BehavioursRobot.with(view)