chromium/ui/android/junit/src/org/chromium/ui/modaldialog/PendingDialogContainerTest.java

// Copyright 2022 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.modaldialog;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import androidx.test.filters.SmallTest;

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

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogPriority;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
import org.chromium.ui.modelutil.PropertyModel;

import java.util.HashSet;
import java.util.function.Consumer;

/** Robolectric tests for testing the functionalities of {@link PendingDialogContainer}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class PendingDialogContainerTest {
    private PendingDialogContainer mPendingDialogContainer;

    @Mock private Consumer<PropertyModel> mPropertyModelConsumerMock;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mPendingDialogContainer = new PendingDialogContainer();
    }

    @Test
    @SmallTest
    public void testSimpleInsertion() {
        PropertyModel model = new PropertyModel();
        mPendingDialogContainer.put(
                ModalDialogType.APP, ModalDialogPriority.LOW, model, /* showAsNext= */ false);
        assertTrue("Model should exist after insertion.", mPendingDialogContainer.contains(model));
    }

    @Test
    @SmallTest
    public void testMultipleInsertion_WithShowAsNextSpecified() {
        PropertyModel model1 = new PropertyModel();
        PropertyModel model2 = new PropertyModel();

        mPendingDialogContainer.put(
                ModalDialogType.APP, ModalDialogPriority.LOW, model1, /* showAsNext= */ false);
        mPendingDialogContainer.put(
                ModalDialogType.APP, ModalDialogPriority.LOW, model2, /* showAsNext= */ true);

        assertTrue(
                "model1 should exist after insertion.", mPendingDialogContainer.contains(model1));
        assertTrue(
                "model2 should exist after insertion.", mPendingDialogContainer.contains(model2));

        PendingDialogContainer.PendingDialogType nextDialog =
                mPendingDialogContainer.getNextPendingDialog(new HashSet<>());
        // Model2 should be put in first because |showAsNext| was set to true for it.
        assertEquals(
                "model2 should be the next dialog to be shown because of |showAsNext| to true",
                model2,
                nextDialog.propertyModel);
        assertEquals(
                "The dialog type should never change for a pending dialog.",
                ModalDialogType.APP,
                nextDialog.dialogType);
        assertEquals(
                "The dialog priority should never change for a pending dialog.",
                ModalDialogPriority.LOW,
                nextDialog.dialogPriority);

        // Check getNextPendingDialog removes the property model.
        assertFalse(
                "getNextPendingDialog should have removed the model2 from pending dialog"
                        + " container.",
                mPendingDialogContainer.contains(model2));
        // Check getNextPendingDialog doesn't touch the other property model.
        assertTrue(
                "model1 should continue existing in pending dialog container.",
                mPendingDialogContainer.contains(model1));
    }

    @Test
    @SmallTest
    public void testInsertionWithDifferentPriorities() {
        PropertyModel model1 = new PropertyModel();
        PropertyModel model2 = new PropertyModel();

        mPendingDialogContainer.put(
                ModalDialogType.APP, ModalDialogPriority.LOW, model1, /* showAsNext= */ false);
        mPendingDialogContainer.put(
                ModalDialogType.TAB, ModalDialogPriority.HIGH, model2, /* showAsNext= */ false);

        assertTrue(
                "model1 should exist after insertion.", mPendingDialogContainer.contains(model1));
        assertTrue(
                "model2 should exist after insertion.", mPendingDialogContainer.contains(model2));

        PendingDialogContainer.PendingDialogType nextDialog =
                mPendingDialogContainer.getNextPendingDialog(new HashSet<>());
        // Even though model2 is TAB based dialog, this is the next dialog in line because of its
        // priority.
        assertEquals(
                "model2 should be the next dialog because of higher priority than model1.",
                model2,
                nextDialog.propertyModel);
        assertEquals(
                "The dialog type should never change for a pending dialog.",
                ModalDialogType.TAB,
                nextDialog.dialogType);
        assertEquals(
                "The dialog priority should never change for a pending dialog.",
                ModalDialogPriority.HIGH,
                nextDialog.dialogPriority);

        // Check getNextPendingDialog removes the property model.
        assertFalse(
                "getNextPendingDialog should have removed the model2 from"
                        + " pending dialog container.",
                mPendingDialogContainer.contains(model2));
        // Check getNextPendingDialog doesn't touch the other property model.
        assertTrue(
                "model1 should continue existing in pending dialog container.",
                mPendingDialogContainer.contains(model1));
    }

    @Test
    @SmallTest
    public void testSimpleRemoval() {
        PropertyModel model = new PropertyModel();
        mPendingDialogContainer.put(
                ModalDialogType.APP, ModalDialogPriority.LOW, model, /* showAsNext= */ false);
        assertTrue("model should exist after insertion.", mPendingDialogContainer.contains(model));
        assertTrue(
                "model should have been found and removed.", mPendingDialogContainer.remove(model));
        assertFalse(
                "model should have been removed after remove operation.",
                mPendingDialogContainer.contains(model));
    }

    @Test
    @SmallTest
    public void testRemovalWithConsumer() {
        PropertyModel model = new PropertyModel();
        mPendingDialogContainer.put(
                ModalDialogType.APP, ModalDialogPriority.LOW, model, /* showAsNext= */ false);
        assertTrue("model should exist after insertion.", mPendingDialogContainer.contains(model));

        doNothing().when(mPropertyModelConsumerMock).accept(model);
        assertTrue(
                "model should have been found and removed.",
                mPendingDialogContainer.remove(ModalDialogType.APP, mPropertyModelConsumerMock));
        verify(mPropertyModelConsumerMock).accept(model);
        assertFalse(
                "model should have been removed after remove operation.",
                mPendingDialogContainer.contains(model));
    }

    @Test
    @SmallTest
    public void testMultipleRemovalsWithConsumer_SameDialogType() {
        PropertyModel model1 = new PropertyModel();
        PropertyModel model2 = new PropertyModel();
        mPendingDialogContainer.put(
                ModalDialogType.APP, ModalDialogPriority.LOW, model1, /* showAsNext= */ false);
        mPendingDialogContainer.put(
                ModalDialogType.APP, ModalDialogPriority.LOW, model2, /* showAsNext= */ false);
        assertTrue(
                "model1 should exist after insertion.", mPendingDialogContainer.contains(model1));
        assertTrue(
                "model2 should exist after insertion.", mPendingDialogContainer.contains(model2));

        doNothing().when(mPropertyModelConsumerMock).accept(model1);
        doNothing().when(mPropertyModelConsumerMock).accept(model2);
        assertTrue(
                "App based dialogs should have been found and removed.",
                mPendingDialogContainer.remove(ModalDialogType.APP, mPropertyModelConsumerMock));
        verify(mPropertyModelConsumerMock, times(2)).accept(any());

        assertFalse(
                "model1 should have been removed because it was of APP type.",
                mPendingDialogContainer.contains(model1));
        assertFalse(
                "model2 should have been removed because it was of APP type.",
                mPendingDialogContainer.contains(model2));
        assertTrue("All dialogs should have been removed.", mPendingDialogContainer.isEmpty());
    }

    @Test
    @SmallTest
    public void testMultipleRemovalsWithConsumer_DifferentDialogType() {
        PropertyModel model1 = new PropertyModel();
        PropertyModel model2 = new PropertyModel();
        mPendingDialogContainer.put(
                ModalDialogType.APP, ModalDialogPriority.LOW, model1, /* showAsNext= */ false);
        mPendingDialogContainer.put(
                ModalDialogType.TAB, ModalDialogPriority.LOW, model2, /* showAsNext= */ false);
        assertTrue(
                "model1 should exist after insertion.", mPendingDialogContainer.contains(model1));
        assertTrue(
                "model2 should exist after insertion.", mPendingDialogContainer.contains(model2));

        doNothing().when(mPropertyModelConsumerMock).accept(model1);
        assertTrue(
                "App based dialogs should have been found and removed.",
                mPendingDialogContainer.remove(ModalDialogType.APP, mPropertyModelConsumerMock));
        verify(mPropertyModelConsumerMock).accept(model1);

        assertFalse(
                "model1 should have been removed because it was of APP type.",
                mPendingDialogContainer.contains(model1));
        assertTrue(
                "Nothing should happen to dialogs of type TAB because we only removed"
                        + " APP based dialogs.",
                mPendingDialogContainer.contains(model2));
        assertFalse(
                "Container shouldn't be empty because there's still model2.",
                mPendingDialogContainer.isEmpty());
    }

    @Test
    @SmallTest
    public void testNonExistentenceOfDialogs() {
        PropertyModel model = new PropertyModel();
        assertFalse(
                "Model was not added therefore shouldn't be contained in pending dialog"
                        + " container.",
                mPendingDialogContainer.contains(model));
    }

    @Test
    @SmallTest
    public void testRemovalOfNonExistententPendingDialogs() {
        PropertyModel model = new PropertyModel();
        assertFalse(
                "Model was not added therefore shouldn't have been found and removed.",
                mPendingDialogContainer.remove(model));
    }

    @Test(expected = AssertionError.class)
    @SmallTest
    public void testOutOfBoundValues_LowerBound() {
        PropertyModel model = new PropertyModel();
        mPendingDialogContainer.put(ModalDialogType.APP, 0, model, /* showAsNext= */ false);
    }

    @Test(expected = AssertionError.class)
    @SmallTest
    public void testOutOfBoundValues_UpperBound() {
        PropertyModel model = new PropertyModel();
        mPendingDialogContainer.put(ModalDialogType.APP, 10, model, /* showAsNext= */ false);
    }

    @Test
    @SmallTest
    public void testKeyUniqueness() {
        for (@ModalDialogPriority int priority = ModalDialogPriority.RANGE_MIN;
                priority <= ModalDialogPriority.RANGE_MAX;
                ++priority) {
            for (@ModalDialogType int type = ModalDialogType.RANGE_MIN;
                    type <= ModalDialogType.RANGE_MAX;
                    ++type) {
                mPendingDialogContainer.put(
                        type, priority, new PropertyModel(), /* showAsNext= */ false);
            }
        }
        final int totalPriorities = ModalDialogPriority.NUM_ENTRIES;
        final int totalTypes = ModalDialogType.NUM_ENTRIES;

        assertEquals(
                "mPendingDialogContainer should have created unique keys to map the"
                        + " pending dialog list of distinct type and priority.",
                totalPriorities * totalTypes,
                mPendingDialogContainer.size());
    }
}