chromium/chromecast/base/java/test/org/chromium/chromecast/base/ObservableMiscellaneousTest.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chromecast.base;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;

import org.chromium.base.test.util.Batch;
import org.chromium.chromecast.base.Inheritance.Base;
import org.chromium.chromecast.base.Inheritance.Derived;

import java.util.ArrayList;
import java.util.List;

/**
 * Miscellaneous tests for Observable.
 *
 * This includes advanced behaviors like subscription-currying and correct use of generics.
 */
@RunWith(BlockJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public class ObservableMiscellaneousTest {
    @Test
    public void testMakeNotifyOneAtATime() {
        ReactiveRecorder r = ReactiveRecorder.record(observer -> {
            observer.open(1).close();
            observer.open(2).close();
            observer.open(3).close();
            return Scope.NO_OP;
        });
        r.verify().opened(1).closed(1).opened(2).closed(2).opened(3).closed(3).end();
    }

    @Test
    public void testMakeNotifyAllAtOnce() {
        ReactiveRecorder r = ReactiveRecorder.record(
                observer -> observer.open("a").and(observer.open("b")).and(observer.open("c")));
        r.verify().opened("a").opened("b").opened("c").end();
        r.unsubscribe();
        r.verify().closed("c").closed("b").closed("a").end();
    }

    @Test
    public void testEmpty() {
        ReactiveRecorder r = ReactiveRecorder.record(Observable.empty());
        r.verify().end();
        r.unsubscribe();
        r.verify().end();
    }

    @Test
    public void testAssignEmptyToTypedObservable() {
        Observable<String> a = Observable.empty();
        Observable<Integer> b = Observable.empty();
    }

    @Test
    public void testJustInt() {
        ReactiveRecorder r = ReactiveRecorder.record(Observable.just(100));
        r.verify().opened(100).end();
        r.unsubscribe();
        r.verify().closed(100).end();
    }

    @Test
    public void testJustString() {
        ReactiveRecorder r = ReactiveRecorder.record(Observable.just("hello"));
        r.verify().opened("hello").end();
        r.unsubscribe();
        r.verify().closed("hello").end();
    }

    @Test
    public void testBeingTooCleverWithObserversAndInheritance() {
        Controller<Base> baseController = new Controller<>();
        Controller<Derived> derivedController = new Controller<>();
        List<String> result = new ArrayList<>();
        // Test that the same Observer object can observe Observables of different types, as
        // long as the Observer type is a superclass of both Observable types.
        Observer<Base> observer = (Base value) -> {
            result.add("enter: " + value.toString());
            return () -> result.add("exit: " + value.toString());
        };
        baseController.subscribe(observer);
        // Compile error if generics are wrong.
        derivedController.subscribe(observer);
        baseController.set(new Base());
        // The scope from the previous set() call will not be overridden because this is activating
        // a different Controller.
        derivedController.set(new Derived());
        // The Controller<Base> can be activated with an object that extends Base.
        baseController.set(new Derived());
        assertThat(
                result, contains("enter: Base", "enter: Derived", "exit: Base", "enter: Derived"));
    }

    @Test
    public void testsubscribeCurrying() {
        Controller<String> aState = new Controller<>();
        Controller<String> bState = new Controller<>();
        Controller<String> result = new Controller<>();
        ReactiveRecorder recorder = ReactiveRecorder.record(result);
        // I guess this makes .and() obsolete?
        aState.subscribe(a -> bState.subscribe(b -> {
            result.set("" + a + ", " + b);
            return () -> result.reset();
        }));
        aState.set("A");
        bState.set("B");
        recorder.verify().opened("A, B").end();
        aState.reset();
        recorder.verify().closed("A, B").end();
        aState.set("AA");
        recorder.verify().opened("AA, B").end();
        bState.reset();
        recorder.verify().closed("AA, B").end();
    }

    @Test
    public void testPowerUnlimitedPower() {
        Controller<Unit> aState = new Controller<>();
        Controller<Unit> bState = new Controller<>();
        Controller<Unit> cState = new Controller<>();
        Controller<Unit> dState = new Controller<>();
        List<String> result = new ArrayList<>();
        // Praise be to Haskell Curry.
        aState.subscribe(a -> bState.subscribe(b -> cState.subscribe(c -> dState.subscribe(d -> {
            result.add("it worked!");
            return () -> result.add("exit");
        }))));
        aState.set(Unit.unit());
        bState.set(Unit.unit());
        cState.set(Unit.unit());
        dState.set(Unit.unit());
        assertThat(result, contains("it worked!"));
        result.clear();
        aState.reset();
        assertThat(result, contains("exit"));
        result.clear();
        aState.set(Unit.unit());
        assertThat(result, contains("it worked!"));
        result.clear();
        bState.reset();
        assertThat(result, contains("exit"));
        result.clear();
        bState.set(Unit.unit());
        assertThat(result, contains("it worked!"));
        result.clear();
        cState.reset();
        assertThat(result, contains("exit"));
        result.clear();
        cState.set(Unit.unit());
        assertThat(result, contains("it worked!"));
        result.clear();
        dState.reset();
        assertThat(result, contains("exit"));
        result.clear();
        dState.set(Unit.unit());
        assertThat(result, contains("it worked!"));
    }

    // Any Scope implementation with a constructor of one argument can use a method reference to its
    // constructor as an Observer.
    private static class TransitionLogger implements Scope {
        public static final List<String> sResult = new ArrayList<>();
        private final String mData;

        public TransitionLogger(String data) {
            mData = data;
            sResult.add("enter: " + mData);
        }

        @Override
        public void close() {
            sResult.add("exit: " + mData);
        }
    }

    @Test
    public void testObserverWithAutoCloseableConstructor() {
        Controller<String> controller = new Controller<>();
        // You can use a constructor method reference in a subscribe() call.
        controller.subscribe(TransitionLogger::new);
        controller.set("a");
        controller.reset();
        assertThat(TransitionLogger.sResult, contains("enter: a", "exit: a"));
    }
}