/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.model import android.util.Pair import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.util.Executors import com.android.launcher3.util.IntArray import com.android.launcher3.util.TestUtil.runOnExecutorSync import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.times import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.same import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever /** Tests for [AddWorkspaceItemsTask] */ @SmallTest @RunWith(AndroidJUnit4::class) class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { private lateinit var mDataModelCallbacks: MyCallbacks private val mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder = mock() @Before override fun setup() { super.setup() mDataModelCallbacks = MyCallbacks() Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) } .get() } @After override fun tearDown() { super.tearDown() } @Test fun givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItem() { val itemToAdd = getNewItem() val nonEmptyScreenIds = listOf(0, 1, 2) givenNewItemSpaces(NewItemSpace(1, 2, 2)) val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) assertThat(addedItems.size).isEqualTo(1) assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) assertThat(addedItems.first().isAnimated).isTrue() verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) } @Test fun givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItem() { val itemsToAdd = arrayOf(getNewItem(), getExistingItem()) givenNewItemSpaces(NewItemSpace(1, 0, 0)) val nonEmptyScreenIds = listOf(0) val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) assertThat(addedItems.size).isEqualTo(1) assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) assertThat(addedItems.first().isAnimated).isTrue() verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) } @Test fun givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItem() { val itemToAdd = getExistingItem() givenNewItemSpaces(NewItemSpace(1, 0, 0)) val nonEmptyScreenIds = listOf(0) val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) assertThat(addedItems.size).isEqualTo(0) // b/343530737 verifyNoMoreInteractions(mWorkspaceItemSpaceFinder) } @Test fun givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenId() { val itemToAdd = getNewItem() givenNewItemSpaces(NewItemSpace(2, 1, 3)) val nonEmptyScreenIds = listOf(0, 2, 3) val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) assertThat(addedItems.size).isEqualTo(1) assertThat(addedItems.first().itemInfo.screenId).isEqualTo(2) assertThat(addedItems.first().isAnimated).isTrue() verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) } @Test fun givenMultipleItems_whenExecuteTask_thenAddThem() { val itemsToAdd = arrayOf( getNewItem(), getExistingItem(), getNewItem(), getNewItem(), getExistingItem(), ) givenNewItemSpaces( NewItemSpace(1, 3, 3), NewItemSpace(2, 0, 0), NewItemSpace(2, 0, 1), ) val nonEmptyScreenIds = listOf(0, 1) val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) // Only the new items should be added assertThat(addedItems.size).isEqualTo(3) // Items that are added to the first screen should not be animated val itemsAddedToFirstScreen = addedItems.filter { it.itemInfo.screenId == 1 } assertThat(itemsAddedToFirstScreen.size).isEqualTo(1) assertThat(itemsAddedToFirstScreen.first().isAnimated).isFalse() // Items that are added to the second screen should be animated val itemsAddedToSecondScreen = addedItems.filter { it.itemInfo.screenId == 2 } assertThat(itemsAddedToSecondScreen.size).isEqualTo(2) itemsAddedToSecondScreen.forEach { assertThat(it.isAnimated).isTrue() } verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 3) } /** Sets up the item space data that will be returned from WorkspaceItemSpaceFinder. */ private fun givenNewItemSpaces(vararg newItemSpaces: NewItemSpace) { val spaceStack = newItemSpaces.toMutableList() whenever( mWorkspaceItemSpaceFinder.findSpaceForItem(any(), any(), any(), any(), any(), any()) ) .then { spaceStack.removeFirst().toIntArray() } } /** * Verifies if WorkspaceItemSpaceFinder was called with proper arguments and how many times was * it called. */ private fun verifyItemSpaceFinderCall(nonEmptyScreenIds: List, numberOfExpectedCall: Int) { verify(mWorkspaceItemSpaceFinder, times(numberOfExpectedCall)) .findSpaceForItem( same(mAppState), same(mModelHelper.bgDataModel), eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())), eq(IntArray()), eq(1), eq(1) ) } /** * Sets up the workspaces with items, executes the task, collects the added items from the model * callback then returns it. */ private fun testAddItems( nonEmptyScreenIds: List, vararg itemsToAdd: WorkspaceItemInfo ): List { setupWorkspaces(nonEmptyScreenIds) val task = newTask(*itemsToAdd) val addedItems = mutableListOf() runOnExecutorSync(Executors.MODEL_EXECUTOR) { mDataModelCallbacks.addedItems.clear() mModelHelper.model.enqueueModelUpdateTask(task) runOnExecutorSync(Executors.MAIN_EXECUTOR) {} addedItems.addAll(mDataModelCallbacks.addedItems) } return addedItems } /** * Creates the task with the given items and replaces the WorkspaceItemSpaceFinder dependency * with a mock. */ private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask = items .map { Pair.create(it, Any()) } .toMutableList() .let { AddWorkspaceItemsTask(it, mWorkspaceItemSpaceFinder) } } private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean) private class MyCallbacks : BgDataModel.Callbacks { val addedItems = mutableListOf() override fun bindAppsAdded( newScreens: IntArray?, addNotAnimated: ArrayList, addAnimated: ArrayList ) { addedItems.addAll(addAnimated.map { AddedItem(it, true) }) addedItems.addAll(addNotAnimated.map { AddedItem(it, false) }) } }