chromium/ui/android/junit/src/org/chromium/ui/base/ApplicationViewportInsetSupplierTest.java

// Copyright 2020 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.ui.base;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.LooperMode;

import org.chromium.base.Callback;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.ui.mojom.VirtualKeyboardMode;

/** Unit tests for the ApplicationViewportInsetSupplier. */
@RunWith(BaseRobolectricTestRunner.class)
@LooperMode(LooperMode.Mode.LEGACY)
public class ApplicationViewportInsetSupplierTest {
    /** A callback with the ability to get the last value pushed to it. */
    private static class CapturingCallback<T> implements Callback<T> {
        private T mValue;

        @Override
        public void onResult(T result) {
            mValue = result;
        }

        public T getCapturedValue() {
            return mValue;
        }
    }

    private ApplicationViewportInsetSupplier mWindowApplicationInsetSupplier;
    private ObservableSupplierImpl<Integer> mKeyboardInsetSupplier;
    private CapturingCallback<ViewportInsets> mInsetObserver;

    @Before
    public void setUp() {
        mWindowApplicationInsetSupplier = new ApplicationViewportInsetSupplier();
        mKeyboardInsetSupplier = new ObservableSupplierImpl<>();

        mInsetObserver = new CapturingCallback<>();

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_VISUAL);
        mWindowApplicationInsetSupplier.setKeyboardInsetSupplier(mKeyboardInsetSupplier);
        mWindowApplicationInsetSupplier.addObserver(mInsetObserver);

        // Clear the observer initially so tests can check whether it was called.
        mInsetObserver.onResult(null);
    }

    @Test
    public void testSupplierDidNotSetValue() {
        assertNotNull(
                "Supplier should provide a non-null value even when unset.",
                mWindowApplicationInsetSupplier.get());
        assertEquals(
                "Initial value for viewVisibleHeightInset is incorrect.",
                0,
                mWindowApplicationInsetSupplier.get().viewVisibleHeightInset);
        assertEquals(
                "Initial value for webContentsHeightInset is incorrect.",
                0,
                mWindowApplicationInsetSupplier.get().webContentsHeightInset);
        assertEquals(
                "Initial value for visualViewportBottomInset is incorrect.",
                0,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);
    }

    @Test
    public void testKeyboardTriggersObserver() {
        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_VISUAL);

        mKeyboardInsetSupplier.set(5);

        assertEquals(
                "Keyboard does not insets the View's visible height.",
                0,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
        assertEquals(
                "Keyboard outsets the WebContents' visible height in RESIZES_VISUAL.",
                -5,
                mInsetObserver.getCapturedValue().webContentsHeightInset);
        assertEquals(
                "Keyboard insets the visual viewport in RESIZES_VISUAL.",
                5,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(
                VirtualKeyboardMode.OVERLAYS_CONTENT);

        assertEquals(
                "Keyboard does not inset the View's visible height.",
                0,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
        assertEquals(
                "Keyboard outsets WebContents' visible height in OVERLAYS_CONTENT.",
                -5,
                mInsetObserver.getCapturedValue().webContentsHeightInset);
        assertEquals(
                "Keyboard does not inset the visual viewport in OVERLAYS_CONTENT.",
                0,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_CONTENT);

        assertEquals(
                "Keyboard does not insetsthe View's visible height.",
                0,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
        assertEquals(
                "Keyboard does not inset the WebContents' visible height.",
                0,
                mInsetObserver.getCapturedValue().webContentsHeightInset);
        assertEquals(
                "Keyboard does not inset the visual viewport in RESIZES_CONTENT.",
                0,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        mKeyboardInsetSupplier.set(10);

        assertEquals(
                "Keyboard does not inset the View's visible height.",
                0,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
        assertEquals(
                "Keyboard does not inset the WebContents' visible height.",
                0,
                mInsetObserver.getCapturedValue().webContentsHeightInset);
        assertEquals(
                "Keyboard does not inset the visual viewport in RESIZES_CONTENT.",
                0,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);
    }

    @Test
    public void testKeyboardWithAccessory() {
        ObservableSupplierImpl<Integer> accessorySupplier = new ObservableSupplierImpl<>();
        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(accessorySupplier);

        mKeyboardInsetSupplier.set(10);
        accessorySupplier.set(5);

        assertEquals(
                "Only accessory insets the View's visible height.",
                5,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
        assertEquals(
                "Keyboard outsets WebContents' height; accessory has no effect in RESIZES_VISUAL.",
                -10,
                mInsetObserver.getCapturedValue().webContentsHeightInset);
        assertEquals(
                "Both keyboard and accessory inset the visual viewport in RESIZES_VISUAL.",
                15,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_CONTENT);

        assertEquals(
                "Only accessory insets the View's visible height.",
                5,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
        assertEquals(
                "Only the accessory insets WebContents' visible height.",
                5,
                mInsetObserver.getCapturedValue().webContentsHeightInset);
        assertEquals(
                "Neither keyboard nor accessory resize the visual viewport in RESIZES_CONTENT.",
                0,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);
    }

    @Test
    public void testSupplierSetBeforeAddingTriggersObserverOnAdd() {
        ObservableSupplierImpl<Integer> supplier = new ObservableSupplierImpl<>();
        supplier.set(20);

        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(supplier);

        assertEquals(
                "The observer should have been triggered when the supplier was added.",
                20,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
    }

    @Test
    public void testRemovingSupplierTriggersObservers() {
        ObservableSupplierImpl<Integer> accessorySupplier = new ObservableSupplierImpl<>();
        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(accessorySupplier);

        mKeyboardInsetSupplier.set(20);
        accessorySupplier.set(5);

        assertEquals(
                "Observed value should come from accessory.",
                5,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);

        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(null);

        assertEquals(
                "Observed value should be 0 when accessory supplier removed.",
                0,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
    }

    @Test
    public void testAllSuppliersRemoved() {
        ObservableSupplierImpl<Integer> accessorySupplier = new ObservableSupplierImpl<>();
        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(accessorySupplier);

        mKeyboardInsetSupplier.set(10);
        accessorySupplier.set(5);

        assertEquals(
                "View inset is correct.",
                5,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
        assertEquals(
                "WebContents inset is correct.",
                -10,
                mInsetObserver.getCapturedValue().webContentsHeightInset);
        assertEquals(
                "VisualViewport inset is correct.",
                15,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setKeyboardInsetSupplier(null);
        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(null);

        assertEquals(
                "View inset should be 0 with no suppliers attached.",
                0,
                mInsetObserver.getCapturedValue().viewVisibleHeightInset);
        assertEquals(
                "WebContentsinset should be 0 with no suppliers attached.",
                0,
                mInsetObserver.getCapturedValue().webContentsHeightInset);
        assertEquals(
                "VisualViewport inset should be 0 with no suppliers attached.",
                0,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);
    }

    @Test
    public void testVisualViewportBottomInset() {
        ObservableSupplierImpl<Integer> accessorySupplier = new ObservableSupplierImpl<>();
        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(accessorySupplier);

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_VISUAL);

        assertEquals(
                "VisualViewport inset initially 0",
                0,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);

        mKeyboardInsetSupplier.set(10);

        assertEquals(
                "Keyboard insets visual viewport",
                10,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);

        accessorySupplier.set(20);

        assertEquals(
                "Accessory insets visual viewport",
                30,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(null);

        assertEquals(
                "Removing accessory removes visual viewport inset",
                10,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setKeyboardInsetSupplier(null);

        assertEquals(
                "Removing keyboard removes visual viewport inset",
                0,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);
    }

    @Test
    public void testVisualViewportInsetWithVirtualKeyboardModes() {
        ObservableSupplierImpl<Integer> accessorySupplier = new ObservableSupplierImpl<>();
        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(accessorySupplier);

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_VISUAL);

        assertEquals(
                "VisualViewport inset initially 0",
                0,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_CONTENT);

        mKeyboardInsetSupplier.set(10);
        assertEquals(
                "Keyboard doesn't inset visual viewport with RESIZES_CONTENT",
                0,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);

        accessorySupplier.set(20);
        assertEquals(
                "Accessory doesn't inset visual viewport with RESIZES_CONTENT",
                0,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(
                VirtualKeyboardMode.OVERLAYS_CONTENT);

        mKeyboardInsetSupplier.set(12);
        assertEquals(
                "Keyboard doesn't inset visual viewport with OVERLAYS_CONTENT",
                0,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);

        accessorySupplier.set(25);
        assertEquals(
                "Accessory doesn't inset visual viewport with OVERLAYS_CONTENT",
                0,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_VISUAL);
        assertEquals(
                "Accessory and keyboard inset visual viewport with RESIZES_VISUAL",
                37,
                mWindowApplicationInsetSupplier.get().visualViewportBottomInset);
    }

    @Test
    public void testTriggerVisualViewportObserver() {
        assertNull("Observer initially uncalled", mInsetObserver.getCapturedValue());

        ObservableSupplierImpl<Integer> accessorySupplier = new ObservableSupplierImpl<>();
        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(accessorySupplier);
        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_VISUAL);

        mKeyboardInsetSupplier.set(10);

        assertEquals(
                "Keyboard triggers visual viewport observer",
                10,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        accessorySupplier.set(20);

        assertEquals(
                "Accessory triggers visual viewport observer",
                30,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(null);

        assertEquals(
                "Removing accessory supplier triggers visual viewport observer",
                10,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        mWindowApplicationInsetSupplier.setKeyboardInsetSupplier(null);
        assertEquals(
                "Removing keyboard supplier triggers visual viewport observer",
                0,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);
    }

    @Test
    public void testTriggerObserverWithVirtualKeyboardModes() {
        ObservableSupplierImpl<Integer> accessorySupplier = new ObservableSupplierImpl<>();
        mWindowApplicationInsetSupplier.setKeyboardAccessoryInsetSupplier(accessorySupplier);
        mKeyboardInsetSupplier.set(30);
        accessorySupplier.set(15);

        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_CONTENT);

        assertEquals(
                "Initial webContentsHeightInset value is correct",
                15,
                mInsetObserver.getCapturedValue().webContentsHeightInset);
        assertEquals(
                "Initial visualViewportBottomInset value is correct",
                0,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        // Clear the observer so we can confirm it gets called.
        mInsetObserver.onResult(null);
        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(
                VirtualKeyboardMode.OVERLAYS_CONTENT);

        assertEquals(
                "Changing to OVERLAYS_CONTENT triggers observer",
                -30,
                mInsetObserver.getCapturedValue().webContentsHeightInset);

        mInsetObserver.onResult(null);
        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_VISUAL);

        assertEquals(
                "Changing to RESIZES_VISUAL triggers observer",
                45,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);

        mInsetObserver.onResult(null);
        mWindowApplicationInsetSupplier.setVirtualKeyboardMode(VirtualKeyboardMode.RESIZES_CONTENT);

        assertEquals(
                "Changing from RESIZES_VISUAL triggers observer",
                0,
                mInsetObserver.getCapturedValue().visualViewportBottomInset);
    }
}