chromium/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/player/expanded/MenuUnitTest.java

// Copyright 2023 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.chrome.browser.readaloud.player.expanded;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.app.Activity;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;

import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.readaloud.player.R;
import org.chromium.chrome.browser.readaloud.player.expanded.MenuItem.Action;

/** Unit tests for {@link Menu}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class MenuUnitTest {
    private final Activity mActivity;
    private Menu mMenu;
    @Mock Callback<Integer> mHandler;
    @Mock Callback<Boolean> mToggleHandler;

    public MenuUnitTest() {
        mActivity = Robolectric.buildActivity(AppCompatActivity.class).setup().get();
        // Need to set theme before inflating layout.
        mActivity.setTheme(R.style.Theme_BrowserUI_DayNight);
        mMenu = (Menu) mActivity.getLayoutInflater().inflate(R.layout.readaloud_menu, null);
        assertNotNull(mMenu);
    }

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testAddGetItem() {
        MenuItem item = mMenu.addItem(1, 0, "test item", /* header= */ null, Action.NONE);
        MenuItem recievedItem = mMenu.getItem(1);
        assertEquals(item, recievedItem);

        // check if itemId doesn't exist, get null
        assertEquals(mMenu.getItem(3), null);
    }

    @Test
    public void testExpandAction() {
        MenuItem item = mMenu.addItem(1, 0, "Expand action", /* header= */ null, Action.EXPAND);
        assertEquals(mMenu.getItem(1), item);
    }

    @Test
    public void testActionToggle() {
        // addItem and setValue
        MenuItem item = mMenu.addItem(1, 0, "Toggle action", /* header= */ null, Action.TOGGLE);
        item.setValue(true);
        SwitchCompat toggle = (SwitchCompat) item.findViewById(R.id.toggle_switch);
        assertTrue(toggle.isChecked());
        item.setValue(false);
        assertFalse(toggle.isChecked());

        // test toggle listener
        item.setToggleHandler(mToggleHandler);
        item.setValue(false);
        // change the value
        assertTrue(item.getChildAt(0).performClick());
        verify(mToggleHandler).onResult(true);
    }

    @Test
    public void testActionRadio() {
        // addItem and setValue
        MenuItem item = mMenu.addItem(1, 0, "Radio action", /* header= */ null, Action.RADIO);
        item.setValue(true);
        RadioButton radioButton = (RadioButton) item.findViewById(R.id.readaloud_radio_button);
        assertTrue(radioButton.isChecked());
        item.setValue(false);
        assertFalse(radioButton.isChecked());

        // test onClick()
        mMenu.setItemClickHandler(mHandler);
        item.setValue(false);
        assertTrue(item.getChildAt(0).performClick());
        assertTrue(radioButton.isChecked());
        // clicking a checked radio button should leave it checked.
        assertTrue(item.getChildAt(0).performClick());
        assertTrue(radioButton.isChecked());

        verify(mHandler, times(2)).onResult(1);
    }

    @Test
    public void testSetItemEnabled() {
        MenuItem item = mMenu.addItem(1, 0, "test item", /* header= */ null, Action.TOGGLE);
        item.setToggleHandler(mToggleHandler);
        item.setItemEnabled(false);

        item.performClick();
        verify(mToggleHandler, never()).onResult(anyBoolean());
    }

    @Test
    public void testClearItems() {
        MenuItem item = mMenu.addItem(1, 0, "test item", /* header= */ null, Action.NONE);
        assertEquals(item, mMenu.getItem(1));
        mMenu.clearItems();
        assertEquals(null, mMenu.getItem(1));
    }

    @Test
    public void testOnItemClicked() {
        mMenu.setItemClickHandler(mHandler);
        mMenu.onItemClicked(1);
        verify(mHandler).onResult(1);
    }

    @Test
    public void testAddPlayButton_OnPlayButtonClicked() {
        mMenu.setPlayButtonClickHandler(mHandler);
        MenuItem item = mMenu.addItem(1, 0, "test item", /* header= */ null, Action.NONE);
        item.addPlayButton();
        ImageView playButton = (ImageView) item.findViewById(R.id.play_button);
        assertEquals(View.VISIBLE, playButton.getVisibility());

        assertTrue(playButton.performClick());
        verify(mHandler, times(1)).onResult(1);

        reset(mHandler);

        mMenu.onPlayButtonClicked(1);
        verify(mHandler, times(1)).onResult(1);
    }

    @Test
    public void testOnRadioButtonSelected() {
        mMenu.setRadioTrueHandler(mHandler);
        for (int i = 0; i < 3; i++) {
            mMenu.addItem(i, 0, "test item", /* header= */ null, Action.RADIO);
        }
        mMenu.onRadioButtonSelected(0);
        mMenu.onRadioButtonSelected(1);
        verify(mHandler).onResult(1);
        assertFalse(
                ((RadioButton) mMenu.getItem(0).findViewById(R.id.readaloud_radio_button))
                        .isChecked());
        assertFalse(
                ((RadioButton) mMenu.getItem(2).findViewById(R.id.readaloud_radio_button))
                        .isChecked());
    }

    @Test
    public void testMenuItemLayoutInflated() {
        MenuItem item = mMenu.addItem(1, 0, "testToggle", /* header= */ null, Action.TOGGLE);
        LinearLayout layout =
                (LinearLayout)
                        mActivity.getLayoutInflater().inflate(R.layout.readaloud_menu_item, null);
        item.getLayoutSupplier().set(layout);
        SwitchCompat button = (SwitchCompat) item.findViewById(R.id.toggle_switch);
        assertNotNull(button);

        // tests if onInitializeAccessibilityEvent is properly setting the event's checked state to
        // match the button's checked state
        AccessibilityDelegate accessibilityDelegate = layout.getAccessibilityDelegate();
        assertNotNull(accessibilityDelegate);

        AccessibilityEvent event = AccessibilityEvent.obtain();
        event.setAction(AccessibilityEvent.TYPE_VIEW_CLICKED);

        item.setValue(true);
        accessibilityDelegate.onInitializeAccessibilityEvent(layout, event);
        assertEquals(button.isChecked(), event.isChecked());

        item.setValue(false);
        accessibilityDelegate.onInitializeAccessibilityEvent(layout, event);
        assertEquals(button.isChecked(), event.isChecked());

        // tests if onInitializeAccessibilityNodeInfo is properly setting the event's checked state
        // to match the button's checked state and the same for checkable state
        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();

        item.setValue(true);
        item.setItemEnabled(true);
        accessibilityDelegate.onInitializeAccessibilityNodeInfo(layout, info);
        assertEquals(button.isChecked(), info.isChecked());
        assertEquals(button.isEnabled(), info.isEnabled());

        item.setValue(false);
        item.setItemEnabled(false);
        accessibilityDelegate.onInitializeAccessibilityNodeInfo(layout, info);
        assertEquals(button.isChecked(), info.isChecked());
        assertEquals(button.isEnabled(), info.isEnabled());
    }

    @Test
    public void testMenuItemLayoutInflated_NothingForActionExpand() {
        MenuItem item = mMenu.addItem(1, 0, "testExpand", /* header= */ null, Action.EXPAND);
        LinearLayout layout =
                (LinearLayout)
                        mActivity.getLayoutInflater().inflate(R.layout.readaloud_menu_item, null);
        item.getLayoutSupplier().set(layout);

        // accessibility delegate will be null for action items without buttons
        AccessibilityDelegate accessibilityDelegate = layout.getAccessibilityDelegate();
        assertNull(accessibilityDelegate);
    }
}