1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.model 18 19 import android.util.Pair 20 import androidx.test.ext.junit.runners.AndroidJUnit4 21 import androidx.test.filters.SmallTest 22 import com.android.launcher3.model.data.ItemInfo 23 import com.android.launcher3.model.data.WorkspaceItemInfo 24 import com.android.launcher3.util.Executors 25 import com.android.launcher3.util.IntArray 26 import com.android.launcher3.util.TestUtil.runOnExecutorSync 27 import com.google.common.truth.Truth.assertThat 28 import org.junit.After 29 import org.junit.Before 30 import org.junit.Test 31 import org.junit.runner.RunWith 32 import org.mockito.Mockito.times 33 import org.mockito.kotlin.any 34 import org.mockito.kotlin.eq 35 import org.mockito.kotlin.mock 36 import org.mockito.kotlin.same 37 import org.mockito.kotlin.verify 38 import org.mockito.kotlin.verifyNoMoreInteractions 39 import org.mockito.kotlin.whenever 40 41 /** Tests for [AddWorkspaceItemsTask] */ 42 @SmallTest 43 @RunWith(AndroidJUnit4::class) 44 class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { 45 46 private lateinit var mDataModelCallbacks: MyCallbacks 47 48 private val mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder = mock() 49 50 @Before setupnull51 override fun setup() { 52 super.setup() 53 mDataModelCallbacks = MyCallbacks() 54 Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) } 55 .get() 56 } 57 58 @After tearDownnull59 override fun tearDown() { 60 super.tearDown() 61 } 62 63 @Test givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItemnull64 fun givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItem() { 65 val itemToAdd = getNewItem() 66 val nonEmptyScreenIds = listOf(0, 1, 2) 67 givenNewItemSpaces(NewItemSpace(1, 2, 2)) 68 69 val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) 70 71 assertThat(addedItems.size).isEqualTo(1) 72 assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) 73 assertThat(addedItems.first().isAnimated).isTrue() 74 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) 75 } 76 77 @Test givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItemnull78 fun givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItem() { 79 val itemsToAdd = arrayOf(getNewItem(), getExistingItem()) 80 givenNewItemSpaces(NewItemSpace(1, 0, 0)) 81 val nonEmptyScreenIds = listOf(0) 82 83 val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) 84 85 assertThat(addedItems.size).isEqualTo(1) 86 assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) 87 assertThat(addedItems.first().isAnimated).isTrue() 88 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) 89 } 90 91 @Test givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItemnull92 fun givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItem() { 93 val itemToAdd = getExistingItem() 94 givenNewItemSpaces(NewItemSpace(1, 0, 0)) 95 val nonEmptyScreenIds = listOf(0) 96 97 val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) 98 99 assertThat(addedItems.size).isEqualTo(0) 100 // b/343530737 101 verifyNoMoreInteractions(mWorkspaceItemSpaceFinder) 102 } 103 104 @Test givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenIdnull105 fun givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenId() { 106 val itemToAdd = getNewItem() 107 givenNewItemSpaces(NewItemSpace(2, 1, 3)) 108 val nonEmptyScreenIds = listOf(0, 2, 3) 109 110 val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) 111 112 assertThat(addedItems.size).isEqualTo(1) 113 assertThat(addedItems.first().itemInfo.screenId).isEqualTo(2) 114 assertThat(addedItems.first().isAnimated).isTrue() 115 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) 116 } 117 118 @Test givenMultipleItems_whenExecuteTask_thenAddThemnull119 fun givenMultipleItems_whenExecuteTask_thenAddThem() { 120 val itemsToAdd = 121 arrayOf(getNewItem(), getExistingItem(), getNewItem(), getNewItem(), getExistingItem()) 122 givenNewItemSpaces(NewItemSpace(1, 3, 3), NewItemSpace(2, 0, 0), NewItemSpace(2, 0, 1)) 123 val nonEmptyScreenIds = listOf(0, 1) 124 125 val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) 126 127 // Only the new items should be added 128 assertThat(addedItems.size).isEqualTo(3) 129 130 // Items that are added to the first screen should not be animated 131 val itemsAddedToFirstScreen = addedItems.filter { it.itemInfo.screenId == 1 } 132 assertThat(itemsAddedToFirstScreen.size).isEqualTo(1) 133 assertThat(itemsAddedToFirstScreen.first().isAnimated).isFalse() 134 135 // Items that are added to the second screen should be animated 136 val itemsAddedToSecondScreen = addedItems.filter { it.itemInfo.screenId == 2 } 137 assertThat(itemsAddedToSecondScreen.size).isEqualTo(2) 138 itemsAddedToSecondScreen.forEach { assertThat(it.isAnimated).isTrue() } 139 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 3) 140 } 141 142 /** Sets up the item space data that will be returned from WorkspaceItemSpaceFinder. */ givenNewItemSpacesnull143 private fun givenNewItemSpaces(vararg newItemSpaces: NewItemSpace) { 144 val spaceStack = newItemSpaces.toMutableList() 145 whenever( 146 mWorkspaceItemSpaceFinder.findSpaceForItem(any(), any(), any(), any(), any(), any()) 147 ) 148 .then { spaceStack.removeFirst().toIntArray() } 149 } 150 151 /** 152 * Verifies if WorkspaceItemSpaceFinder was called with proper arguments and how many times was 153 * it called. 154 */ verifyItemSpaceFinderCallnull155 private fun verifyItemSpaceFinderCall(nonEmptyScreenIds: List<Int>, numberOfExpectedCall: Int) { 156 verify(mWorkspaceItemSpaceFinder, times(numberOfExpectedCall)) 157 .findSpaceForItem( 158 same(mAppState), 159 same(mModelHelper.bgDataModel), 160 eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())), 161 eq(IntArray()), 162 eq(1), 163 eq(1), 164 ) 165 } 166 167 /** 168 * Sets up the workspaces with items, executes the task, collects the added items from the model 169 * callback then returns it. 170 */ testAddItemsnull171 private fun testAddItems( 172 nonEmptyScreenIds: List<Int>, 173 vararg itemsToAdd: WorkspaceItemInfo, 174 ): List<AddedItem> { 175 setupWorkspaces(nonEmptyScreenIds) 176 val task = newTask(*itemsToAdd) 177 178 val addedItems = mutableListOf<AddedItem>() 179 180 runOnExecutorSync(Executors.MODEL_EXECUTOR) { 181 mDataModelCallbacks.addedItems.clear() 182 mModelHelper.model.enqueueModelUpdateTask(task) 183 runOnExecutorSync(Executors.MAIN_EXECUTOR) {} 184 addedItems.addAll(mDataModelCallbacks.addedItems) 185 } 186 187 return addedItems 188 } 189 190 /** 191 * Creates the task with the given items and replaces the WorkspaceItemSpaceFinder dependency 192 * with a mock. 193 */ newTasknull194 private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask = 195 items 196 .map { Pair.create(it, Any()) } 197 .toMutableList() <lambda>null198 .let { AddWorkspaceItemsTask(it, mWorkspaceItemSpaceFinder) } 199 } 200 201 private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean) 202 203 private class MyCallbacks : BgDataModel.Callbacks { 204 205 val addedItems = mutableListOf<AddedItem>() 206 bindAppsAddednull207 override fun bindAppsAdded( 208 newScreens: IntArray?, 209 addNotAnimated: ArrayList<ItemInfo>, 210 addAnimated: ArrayList<ItemInfo>, 211 ) { 212 addedItems.addAll(addAnimated.map { AddedItem(it, true) }) 213 addedItems.addAll(addNotAnimated.map { AddedItem(it, false) }) 214 } 215 } 216