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.any 27 import com.android.launcher3.util.eq 28 import com.android.launcher3.util.same 29 import com.google.common.truth.Truth.assertThat 30 import org.junit.After 31 import org.junit.Before 32 import org.junit.Test 33 import org.junit.runner.RunWith 34 import org.mockito.ArgumentCaptor 35 import org.mockito.Captor 36 import org.mockito.Mock 37 import org.mockito.Mockito.times 38 import org.mockito.Mockito.verify 39 import org.mockito.Mockito.verifyZeroInteractions 40 import org.mockito.Mockito.`when` as whenever 41 import org.mockito.MockitoAnnotations 42 43 /** Tests for [AddWorkspaceItemsTask] */ 44 @SmallTest 45 @RunWith(AndroidJUnit4::class) 46 class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { 47 48 @Captor private lateinit var mAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>> 49 50 @Captor private lateinit var mNotAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>> 51 52 @Mock private lateinit var mDataModelCallbacks: BgDataModel.Callbacks 53 54 @Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder 55 56 @Before setupnull57 override fun setup() { 58 super.setup() 59 MockitoAnnotations.initMocks(this) 60 Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) } 61 .get() 62 } 63 64 @After tearDownnull65 override fun tearDown() { 66 super.tearDown() 67 } 68 69 @Test givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItemnull70 fun givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItem() { 71 val itemToAdd = getNewItem() 72 val nonEmptyScreenIds = listOf(0, 1, 2) 73 givenNewItemSpaces(NewItemSpace(1, 2, 2)) 74 75 val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) 76 77 assertThat(addedItems.size).isEqualTo(1) 78 assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) 79 assertThat(addedItems.first().isAnimated).isTrue() 80 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) 81 } 82 83 @Test givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItemnull84 fun givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItem() { 85 val itemsToAdd = arrayOf(getNewItem(), getExistingItem()) 86 givenNewItemSpaces(NewItemSpace(1, 0, 0)) 87 val nonEmptyScreenIds = listOf(0) 88 89 val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) 90 91 assertThat(addedItems.size).isEqualTo(1) 92 assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) 93 assertThat(addedItems.first().isAnimated).isTrue() 94 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) 95 } 96 97 @Test givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItemnull98 fun givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItem() { 99 val itemToAdd = getExistingItem() 100 givenNewItemSpaces(NewItemSpace(1, 0, 0)) 101 val nonEmptyScreenIds = listOf(0) 102 103 val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) 104 105 assertThat(addedItems.size).isEqualTo(0) 106 verifyZeroInteractions(mWorkspaceItemSpaceFinder, mDataModelCallbacks) 107 } 108 109 @Test givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenIdnull110 fun givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenId() { 111 val itemToAdd = getNewItem() 112 givenNewItemSpaces(NewItemSpace(2, 1, 3)) 113 val nonEmptyScreenIds = listOf(0, 2, 3) 114 115 val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) 116 117 assertThat(addedItems.size).isEqualTo(1) 118 assertThat(addedItems.first().itemInfo.screenId).isEqualTo(2) 119 assertThat(addedItems.first().isAnimated).isTrue() 120 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) 121 } 122 123 @Test givenMultipleItems_whenExecuteTask_thenAddThemnull124 fun givenMultipleItems_whenExecuteTask_thenAddThem() { 125 val itemsToAdd = 126 arrayOf( 127 getNewItem(), 128 getExistingItem(), 129 getNewItem(), 130 getNewItem(), 131 getExistingItem(), 132 ) 133 givenNewItemSpaces( 134 NewItemSpace(1, 3, 3), 135 NewItemSpace(2, 0, 0), 136 NewItemSpace(2, 0, 1), 137 ) 138 val nonEmptyScreenIds = listOf(0, 1) 139 140 val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) 141 142 // Only the new items should be added 143 assertThat(addedItems.size).isEqualTo(3) 144 145 // Items that are added to the first screen should not be animated 146 val itemsAddedToFirstScreen = addedItems.filter { it.itemInfo.screenId == 1 } 147 assertThat(itemsAddedToFirstScreen.size).isEqualTo(1) 148 assertThat(itemsAddedToFirstScreen.first().isAnimated).isFalse() 149 150 // Items that are added to the second screen should be animated 151 val itemsAddedToSecondScreen = addedItems.filter { it.itemInfo.screenId == 2 } 152 assertThat(itemsAddedToSecondScreen.size).isEqualTo(2) 153 itemsAddedToSecondScreen.forEach { assertThat(it.isAnimated).isTrue() } 154 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 3) 155 } 156 157 /** Sets up the item space data that will be returned from WorkspaceItemSpaceFinder. */ givenNewItemSpacesnull158 private fun givenNewItemSpaces(vararg newItemSpaces: NewItemSpace) { 159 val spaceStack = newItemSpaces.toMutableList() 160 whenever( 161 mWorkspaceItemSpaceFinder.findSpaceForItem(any(), any(), any(), any(), any(), any()) 162 ) 163 .then { spaceStack.removeFirst().toIntArray() } 164 } 165 166 /** 167 * Verifies if WorkspaceItemSpaceFinder was called with proper arguments and how many times was 168 * it called. 169 */ verifyItemSpaceFinderCallnull170 private fun verifyItemSpaceFinderCall(nonEmptyScreenIds: List<Int>, numberOfExpectedCall: Int) { 171 verify(mWorkspaceItemSpaceFinder, times(numberOfExpectedCall)) 172 .findSpaceForItem( 173 same(mAppState), 174 same(mModelHelper.bgDataModel), 175 eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())), 176 eq(IntArray()), 177 eq(1), 178 eq(1) 179 ) 180 } 181 182 /** 183 * Sets up the workspaces with items, executes the task, collects the added items from the model 184 * callback then returns it. 185 */ testAddItemsnull186 private fun testAddItems( 187 nonEmptyScreenIds: List<Int>, 188 vararg itemsToAdd: WorkspaceItemInfo 189 ): List<AddedItem> { 190 setupWorkspaces(nonEmptyScreenIds) 191 val task = newTask(*itemsToAdd) 192 var updateCount = 0 193 mModelHelper.executeTaskForTest(task).forEach { 194 updateCount++ 195 it.run() 196 } 197 198 val addedItems = mutableListOf<AddedItem>() 199 if (updateCount > 0) { 200 verify(mDataModelCallbacks) 201 .bindAppsAdded( 202 any(), 203 mNotAnimatedItemArgumentCaptor.capture(), 204 mAnimatedItemArgumentCaptor.capture() 205 ) 206 addedItems.addAll(mAnimatedItemArgumentCaptor.value.map { AddedItem(it, true) }) 207 addedItems.addAll(mNotAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) }) 208 } 209 210 return addedItems 211 } 212 213 /** 214 * Creates the task with the given items and replaces the WorkspaceItemSpaceFinder dependency 215 * with a mock. 216 */ newTasknull217 private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask = 218 items 219 .map { Pair.create(it, Any()) } 220 .toMutableList() <lambda>null221 .let { AddWorkspaceItemsTask(it, mWorkspaceItemSpaceFinder) } 222 } 223 224 private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean) 225