1/* 2 * Copyright (C) 2017 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 17package androidx.leanback.widget; 18 19import static org.junit.Assert.assertEquals; 20import static org.junit.Assert.assertTrue; 21import static org.mockito.ArgumentMatchers.any; 22import static org.mockito.ArgumentMatchers.anyInt; 23import static org.mockito.ArgumentMatchers.eq; 24import static org.mockito.Mockito.atLeast; 25import static org.mockito.Mockito.never; 26import static org.mockito.Mockito.times; 27 28import android.content.Context; 29import android.os.Bundle; 30import android.support.test.InstrumentationRegistry; 31import android.support.test.filters.SmallTest; 32import android.view.View; 33import android.view.ViewGroup; 34import android.widget.FrameLayout; 35 36import androidx.annotation.Nullable; 37import androidx.leanback.R; 38import androidx.recyclerview.widget.RecyclerView; 39 40import org.junit.Before; 41import org.junit.Test; 42import org.junit.runner.RunWith; 43import org.junit.runners.JUnit4; 44import org.mockito.Mockito; 45 46import java.util.ArrayList; 47import java.util.List; 48 49@SmallTest 50@RunWith(JUnit4.class) 51public class ObjectAdapterTest { 52 53 private static final String ID = "id"; 54 private static final String STRING_MEMBER_ONE = "stringMemberOne"; 55 private static final String STRING_MEMBER_TWO = "stringMemberTwo"; 56 private static final String NOT_RELATED_STRING_MEMBER = "notRelatedStringMember"; 57 58 protected ItemBridgeAdapter mBridgeAdapter; 59 protected ArrayObjectAdapter mAdapter; 60 61 private ArrayList mItems; 62 private DiffCallback<AdapterItem> mMockedCallback; 63 private DiffCallback<AdapterItem> mCallbackWithoutPayload; 64 private RecyclerView.AdapterDataObserver mObserver; 65 66 private Context mContext; 67 private ListRowPresenter mListRowPresenter; 68 private ListRowPresenter.ViewHolder mListVh; 69 private ArrayObjectAdapter mRowsAdapter; 70 private AdapterItemPresenter mAdapterItemPresenter; 71 72 private ListRow mRow; 73 74 /** 75 * This type is used to test setItems() API. 76 */ 77 private static class AdapterItem { 78 private int mId; 79 private String mStringMemberOne; 80 81 // mStringMemberTwo is only used to test if correct payload can be generated. 82 private String mStringMemberTwo; 83 84 // not related string will not impact the result of our equals function. 85 // Used to verify if payload computing process still honor the rule set by 86 // areContentsTheSame() method 87 private String mNotRelatedStringMember; 88 89 AdapterItem(int id, String stringMemberOne) { 90 mId = id; 91 mStringMemberOne = stringMemberOne; 92 mStringMemberTwo = ""; 93 mNotRelatedStringMember = ""; 94 } 95 96 AdapterItem(int id, String stringMemberOne, String stringMemberTwo) { 97 mId = id; 98 mStringMemberOne = stringMemberOne; 99 mStringMemberTwo = stringMemberTwo; 100 mNotRelatedStringMember = ""; 101 } 102 103 AdapterItem(int id, String stringMemberOne, String stringMemberTwo, 104 String notRelatedStringMember) { 105 mId = id; 106 mStringMemberOne = stringMemberOne; 107 mStringMemberTwo = stringMemberTwo; 108 mNotRelatedStringMember = notRelatedStringMember; 109 } 110 111 public int getId() { 112 return mId; 113 } 114 115 public String getStringMemberOne() { 116 return mStringMemberOne; 117 } 118 119 public String getStringMemberTwo() { 120 return mStringMemberTwo; 121 } 122 123 public String getNotRelatedStringMember() { 124 return mNotRelatedStringMember; 125 } 126 127 @Override 128 public boolean equals(Object o) { 129 if (this == o) return true; 130 if (o == null || getClass() != o.getClass()) return false; 131 132 AdapterItem that = (AdapterItem) o; 133 134 if (mId != that.mId) return false; 135 if (mStringMemberOne != null ? !mStringMemberOne.equals(that.mStringMemberOne) 136 : that.mStringMemberOne != null) { 137 return false; 138 } 139 return mStringMemberTwo != null ? mStringMemberTwo.equals(that.mStringMemberTwo) 140 : that.mStringMemberTwo == null; 141 } 142 143 @Override 144 public int hashCode() { 145 int result = mId; 146 result = 31 * result + (mStringMemberOne != null ? mStringMemberOne.hashCode() : 0); 147 result = 31 * result + (mStringMemberTwo != null ? mStringMemberTwo.hashCode() : 0); 148 return result; 149 } 150 } 151 152 /** 153 * Extend from DiffCallback extended class to define the rule to compare if two items are the 154 * same/ have the same content and how to calculate the payload. 155 * 156 * The payload will only be calculated when the two items are the same but with different 157 * contents. So we make this class as a public class which can be mocked by mockito to verify 158 * if the calculation process satisfies our requirement. 159 */ 160 public static class DiffCallbackPayloadTesting extends DiffCallback<AdapterItem> { 161 // Using item's mId as the standard to judge if two items is the same 162 @Override 163 public boolean areItemsTheSame(AdapterItem oldItem, AdapterItem newItem) { 164 return oldItem.getId() == newItem.getId(); 165 } 166 167 // Using equals method to judge if two items have the same content. 168 @Override 169 public boolean areContentsTheSame(AdapterItem oldItem, AdapterItem newItem) { 170 return oldItem.equals(newItem); 171 } 172 173 @Nullable 174 @Override 175 public Object getChangePayload(AdapterItem oldItem, 176 AdapterItem newItem) { 177 Bundle diff = new Bundle(); 178 if (oldItem.getId() != newItem.getId()) { 179 diff.putInt(ID, newItem.getId()); 180 } 181 182 if (!oldItem.getStringMemberOne().equals(newItem.getStringMemberOne())) { 183 diff.putString(STRING_MEMBER_ONE, newItem.getStringMemberOne()); 184 } 185 186 if (!oldItem.getStringMemberTwo().equals(newItem.getStringMemberTwo())) { 187 diff.putString(STRING_MEMBER_TWO, newItem.getStringMemberTwo()); 188 } 189 190 if (!oldItem.getNotRelatedStringMember().equals(newItem.getNotRelatedStringMember())) { 191 diff.putString(NOT_RELATED_STRING_MEMBER, newItem.getNotRelatedStringMember()); 192 } 193 194 if (diff.size() == 0) { 195 return null; 196 } 197 return diff; 198 } 199 } 200 201 /** 202 * The presenter designed for adapter item. 203 * 204 * The reason to set this class as a public class is for Mockito to mock it. So we can observe 205 * method's dispatching easily 206 */ 207 public static class AdapterItemPresenter extends Presenter { 208 int mWidth; 209 int mHeight; 210 211 AdapterItemPresenter() { 212 this(100, 100); 213 } 214 215 AdapterItemPresenter(int width, int height) { 216 mWidth = width; 217 mHeight = height; 218 } 219 220 @Override 221 public ViewHolder onCreateViewHolder(ViewGroup parent) { 222 View view = new View(parent.getContext()); 223 view.setFocusable(true); 224 view.setId(R.id.lb_action_button); 225 view.setLayoutParams(new ViewGroup.LayoutParams(mWidth, mHeight)); 226 return new Presenter.ViewHolder(view); 227 } 228 229 @Override 230 public void onBindViewHolder(ViewHolder viewHolder, Object item) { 231 // no - op 232 } 233 234 @Override 235 public void onUnbindViewHolder(ViewHolder viewHolder) { 236 // no - op 237 } 238 239 @Override 240 public void onBindViewHolder(ViewHolder viewHolder, Object item, 241 List<Object> payloads) { 242 // no - op 243 } 244 } 245 246 247 /** 248 * Initialize test-related members. 249 */ 250 @Before 251 public void setup() { 252 mAdapter = new ArrayObjectAdapter(); 253 mBridgeAdapter = new ItemBridgeAdapter(mAdapter); 254 mItems = new ArrayList(); 255 mMockedCallback = Mockito.spy(DiffCallbackPayloadTesting.class); 256 257 // the diff callback without calculating the payload 258 mCallbackWithoutPayload = new DiffCallback<AdapterItem>() { 259 260 // Using item's mId as the standard to judge if two items is the same 261 @Override 262 public boolean areItemsTheSame(AdapterItem oldItem, AdapterItem newItem) { 263 return oldItem.getId() == newItem.getId(); 264 } 265 266 // Using equals method to judge if two items have the same content. 267 @Override 268 public boolean areContentsTheSame(AdapterItem oldItem, AdapterItem newItem) { 269 return oldItem.equals(newItem); 270 } 271 }; 272 273 // Spy the RecyclerView.AdapterObserver 274 mObserver = Mockito.spy(RecyclerView.AdapterDataObserver.class); 275 276 // register observer so we can observe the events 277 mBridgeAdapter.registerAdapterDataObserver(mObserver); 278 279 // obtain context through instrumentation registry 280 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 281 282 // 283 ListRowPresenter listRowPresenter = new ListRowPresenter(); 284 mListRowPresenter = Mockito.spy(listRowPresenter); 285 286 // mock item presenter 287 AdapterItemPresenter adapterItemPresenter = new AdapterItemPresenter(); 288 mAdapterItemPresenter = Mockito.spy(adapterItemPresenter); 289 mRow = new ListRow(new ArrayObjectAdapter(mAdapterItemPresenter)); 290 } 291 292 /** 293 * The following test case is mainly focused on the basic functionality provided by 294 * Object Adapter. 295 * 296 * The key purpose for this test is to make sure when adapter send out a signal through 297 * notify function, it will finally be intercepted by recycler view's observer 298 */ 299 @Test 300 public void testBasicFunctionality() { 301 mItems.add("a"); 302 mItems.add("b"); 303 mItems.add("c"); 304 mAdapter.addAll(0, mItems); 305 306 // size 307 assertEquals(mAdapter.size(), 3); 308 309 // get 310 assertEquals(mAdapter.get(0), "a"); 311 assertEquals(mAdapter.get(1), "b"); 312 assertEquals(mAdapter.get(2), "c"); 313 314 // indexOf 315 assertEquals(mAdapter.indexOf("a"), 0); 316 assertEquals(mAdapter.indexOf("b"), 1); 317 assertEquals(mAdapter.indexOf("c"), 2); 318 319 // insert 320 mAdapter.add(1, "a1"); 321 Mockito.verify(mObserver).onItemRangeInserted(1, 1); 322 assertAdapterContent(mAdapter, new Object[]{"a", "a1", "b", "c"}); 323 Mockito.reset(mObserver); 324 325 // insert multiple 326 ArrayList newItems1 = new ArrayList(); 327 newItems1.add("a2"); 328 newItems1.add("a3"); 329 mAdapter.addAll(1, newItems1); 330 Mockito.verify(mObserver).onItemRangeInserted(1, 2); 331 assertAdapterContent(mAdapter, new Object[]{"a", "a2", "a3", "a1", "b", "c"}); 332 Mockito.reset(mObserver); 333 334 // update 335 mAdapter.notifyArrayItemRangeChanged(2, 3); 336 Mockito.verify(mObserver).onItemRangeChanged(2, 3); 337 assertAdapterContent(mAdapter, new Object[]{"a", "a2", "a3", "a1", "b", "c"}); 338 Mockito.reset(mObserver); 339 340 // remove 341 mAdapter.removeItems(1, 4); 342 Mockito.verify(mObserver).onItemRangeRemoved(1, 4); 343 assertAdapterContent(mAdapter, new Object[]{"a", "c"}); 344 Mockito.reset(mObserver); 345 346 // move 347 mAdapter.move(0, 1); 348 Mockito.verify(mObserver).onItemRangeMoved(0, 1, 1); 349 assertAdapterContent(mAdapter, new Object[]{"c", "a"}); 350 Mockito.reset(mObserver); 351 352 // replace 353 mAdapter.replace(0, "a"); 354 Mockito.verify(mObserver).onItemRangeChanged(0, 1); 355 assertAdapterContent(mAdapter, new Object[]{"a", "a"}); 356 Mockito.reset(mObserver); 357 mAdapter.replace(1, "b"); 358 Mockito.verify(mObserver).onItemRangeChanged(1, 1); 359 assertAdapterContent(mAdapter, new Object[]{"a", "b"}); 360 Mockito.reset(mObserver); 361 362 // remove multiple 363 mItems.clear(); 364 mItems.add("a"); 365 mItems.add("b"); 366 mAdapter.addAll(0, mItems); 367 mAdapter.removeItems(0, 2); 368 Mockito.verify(mObserver).onItemRangeRemoved(0, 2); 369 assertAdapterContent(mAdapter, new Object[]{"a", "b"}); 370 Mockito.reset(mObserver); 371 372 // clear 373 mAdapter.clear(); 374 Mockito.verify(mObserver).onItemRangeRemoved(0, 2); 375 assertAdapterContent(mAdapter, new Object[]{}); 376 Mockito.reset(mObserver); 377 378 // isImmediateNotifySupported 379 assertTrue(mAdapter.isImmediateNotifySupported()); 380 } 381 382 383 @Test 384 public void testSetItemsNoDiffCallback() { 385 mItems.add(new AdapterItem(1, "a")); 386 mItems.add(new AdapterItem(2, "b")); 387 mItems.add(new AdapterItem(3, "c")); 388 mAdapter.setItems(mItems, null); 389 Mockito.verify(mObserver, times(1)).onChanged(); 390 Mockito.verify(mObserver, never()).onItemRangeInserted(anyInt(), anyInt()); 391 Mockito.verify(mObserver, never()).onItemRangeRemoved(anyInt(), anyInt()); 392 Mockito.verify(mObserver, never()).onItemRangeMoved(anyInt(), anyInt(), anyInt()); 393 394 mItems.add(new AdapterItem(4, "a")); 395 mItems.add(new AdapterItem(5, "b")); 396 mItems.add(new AdapterItem(6, "c")); 397 mAdapter.setItems(mItems, null); 398 Mockito.verify(mObserver, times(2)).onChanged(); 399 Mockito.verify(mObserver, never()).onItemRangeInserted(anyInt(), anyInt()); 400 Mockito.verify(mObserver, never()).onItemRangeRemoved(anyInt(), anyInt()); 401 Mockito.verify(mObserver, never()).onItemRangeMoved(anyInt(), anyInt(), anyInt()); 402 } 403 404 /** 405 * The following test cases are mainly focused on the basic functionality provided by setItems 406 * function 407 * 408 * It can be deemed as an extension to the previous test, and won't consider payload in this 409 * test case. 410 * 411 * Test0 will treat all items as the same item with same content. 412 */ 413 @Test 414 public void testSetItemsMethod0() { 415 mItems.add("a"); 416 mItems.add("b"); 417 mItems.add("c"); 418 419 DiffCallback<String> callback = new DiffCallback<String>() { 420 421 // Always treat two items are the same. 422 @Override 423 public boolean areItemsTheSame(String oldItem, String newItem) { 424 return true; 425 } 426 427 // Always treat two items have the same content. 428 @Override 429 public boolean areContentsTheSame(String oldItem, String newItem) { 430 return true; 431 } 432 }; 433 434 mAdapter.setItems(mItems, callback); 435 // verify method dispatching 436 Mockito.verify(mObserver).onItemRangeInserted(0, 3); 437 438 // Clear previous items and set a new list of items. 439 mItems.clear(); 440 mItems.add("a"); 441 mItems.add("b"); 442 mItems.add("c"); 443 444 // reset mocked object before calling setItems method 445 Mockito.reset(mObserver); 446 mAdapter.setItems(mItems, callback); 447 448 // verify method dispatching 449 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), any()); 450 Mockito.verify(mObserver, never()).onItemRangeMoved(anyInt(), anyInt(), anyInt()); 451 Mockito.verify(mObserver, never()).onItemRangeRemoved(anyInt(), anyInt()); 452 Mockito.verify(mObserver, never()).onItemRangeInserted(anyInt(), anyInt()); 453 assertAdapterContent(mAdapter, new Object[]{"a", "b", "c"}); 454 } 455 456 /** 457 * Test1 will treat all items as the same item with same content. 458 */ 459 @Test 460 public void testSetItemsMethod1() { 461 mItems.add("a"); 462 mItems.add("b"); 463 mItems.add("c"); 464 465 DiffCallback<String> callback = new DiffCallback<String>() { 466 467 // Always treat two items are the different. 468 @Override 469 public boolean areItemsTheSame(String oldItem, String newItem) { 470 return false; 471 } 472 473 // Always treat two items have the different content. 474 @Override 475 public boolean areContentsTheSame(String oldItem, String newItem) { 476 return false; 477 } 478 }; 479 480 mAdapter.setItems(mItems, callback); 481 // verify method dispatching 482 Mockito.verify(mObserver).onItemRangeInserted(0, 3); 483 484 // Clear previous items and set a new list of items. 485 mItems.clear(); 486 mItems.add("a"); 487 mItems.add("b"); 488 mItems.add("c"); 489 490 // reset mocked object before calling setItems method 491 Mockito.reset(mObserver); 492 mAdapter.setItems(mItems, callback); 493 494 // No change or move event should be fired under current callback. 495 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), any()); 496 Mockito.verify(mObserver, never()).onItemRangeMoved(anyInt(), anyInt(), anyInt()); 497 Mockito.verify(mObserver).onItemRangeRemoved(0, 3); 498 Mockito.verify(mObserver).onItemRangeInserted(0, 3); 499 assertAdapterContent(mAdapter, new Object[]{"a", "b", "c"}); 500 } 501 502 /** 503 * Test2 will trigger notifyItemRangeChanged event 504 */ 505 @Test 506 public void testSetItemsMethod2() { 507 // initial item list 508 mItems.add(new AdapterItem(1, "a")); 509 mItems.add(new AdapterItem(2, "b")); 510 mItems.add(new AdapterItem(3, "c")); 511 mAdapter.setItems(mItems, mCallbackWithoutPayload); 512 513 // Clear previous items and set a new list of items. 514 mItems.clear(); 515 mItems.add(new AdapterItem(1, "a")); 516 mItems.add(new AdapterItem(2, "c")); 517 mItems.add(new AdapterItem(3, "b")); 518 519 // reset mocked object before calling setItems method 520 Mockito.reset(mObserver); 521 mAdapter.setItems(mItems, mCallbackWithoutPayload); 522 523 // verify method dispatching 524 Mockito.verify(mObserver).onItemRangeChanged(1, 2, null); 525 } 526 527 528 /** 529 * Test3 will trigger notifyItemMoved event 530 */ 531 @Test 532 public void testSetItemsMethod3() { 533 // initial item list 534 mItems.add(new AdapterItem(1, "a")); 535 mItems.add(new AdapterItem(2, "b")); 536 mItems.add(new AdapterItem(3, "c")); 537 mAdapter.setItems(mItems, mCallbackWithoutPayload); 538 539 // Clear previous items and set a new list of items. 540 mItems.clear(); 541 mItems.add(new AdapterItem(2, "b")); 542 mItems.add(new AdapterItem(1, "a")); 543 mItems.add(new AdapterItem(3, "c")); 544 545 // reset mocked object before calling setItems method 546 Mockito.reset(mObserver); 547 mAdapter.setItems(mItems, mCallbackWithoutPayload); 548 549 // verify method dispatching 550 Mockito.verify(mObserver).onItemRangeMoved(1, 0, 1); 551 } 552 553 /** 554 * Test4 will trigger notifyItemRangeRemoved event 555 */ 556 @Test 557 public void testSetItemsMethod4() { 558 // initial item list 559 mItems.add(new AdapterItem(1, "a")); 560 mItems.add(new AdapterItem(2, "b")); 561 mItems.add(new AdapterItem(3, "c")); 562 mAdapter.setItems(mItems, mCallbackWithoutPayload); 563 564 // Clear previous items and set a new list of items. 565 mItems.clear(); 566 mItems.add(new AdapterItem(2, "b")); 567 mItems.add(new AdapterItem(3, "c")); 568 569 // reset mocked object before calling setItems method 570 Mockito.reset(mObserver); 571 mAdapter.setItems(mItems, mCallbackWithoutPayload); 572 573 // verify method dispatching 574 Mockito.verify(mObserver).onItemRangeRemoved(0, 1); 575 } 576 577 /** 578 * Test5 will trigger notifyItemRangeInserted event 579 */ 580 @Test 581 public void testSetItemsMethod5() { 582 // initial item list 583 mItems.add(new AdapterItem(1, "a")); 584 mItems.add(new AdapterItem(2, "b")); 585 mItems.add(new AdapterItem(3, "c")); 586 mAdapter.setItems(mItems, mCallbackWithoutPayload); 587 588 // Clear previous items and set a new list of items. 589 mItems.clear(); 590 mItems.add(new AdapterItem(1, "a")); 591 mItems.add(new AdapterItem(2, "b")); 592 mItems.add(new AdapterItem(3, "c")); 593 mItems.add(new AdapterItem(4, "d")); 594 595 // reset mocked object before calling setItems method 596 Mockito.reset(mObserver); 597 mAdapter.setItems(mItems, mCallbackWithoutPayload); 598 599 // verify method dispatching 600 Mockito.verify(mObserver).onItemRangeInserted(3, 1); 601 } 602 603 604 /** 605 * Test6 will trigger notifyItemRangeInserted event and notifyItemRangeRemoved event 606 * simultaneously 607 */ 608 @Test 609 public void testSetItemsMethod6() { 610 // initial item list 611 mItems.add(new AdapterItem(1, "a")); 612 mItems.add(new AdapterItem(2, "b")); 613 mItems.add(new AdapterItem(3, "c")); 614 mAdapter.setItems(mItems, mCallbackWithoutPayload); 615 616 // Clear previous items and set a new list of items. 617 mItems.clear(); 618 mItems.add(new AdapterItem(2, "a")); 619 mItems.add(new AdapterItem(2, "b")); 620 mItems.add(new AdapterItem(3, "c")); 621 622 // reset mocked object before calling setItems method 623 Mockito.reset(mObserver); 624 mAdapter.setItems(mItems, mCallbackWithoutPayload); 625 626 // verify method dispatching 627 Mockito.verify(mObserver).onItemRangeRemoved(0, 1); 628 Mockito.verify(mObserver).onItemRangeInserted(0, 1); 629 } 630 631 /** 632 * Test7 will trigger notifyItemRangeMoved and notifyItemRangeChanged event simultaneously 633 */ 634 @Test 635 public void testItemsMethod7() { 636 // initial item list 637 mItems.add(new AdapterItem(1, "a")); 638 mItems.add(new AdapterItem(2, "b")); 639 mItems.add(new AdapterItem(3, "c")); 640 mAdapter.setItems(mItems, mCallbackWithoutPayload); 641 642 // Clear previous items and set a new list of items. 643 mItems.clear(); 644 mItems.add(new AdapterItem(1, "aa")); 645 mItems.add(new AdapterItem(3, "c")); 646 mItems.add(new AdapterItem(2, "b")); 647 648 // reset mocked object before calling setItems method 649 Mockito.reset(mObserver); 650 mAdapter.setItems(mItems, mCallbackWithoutPayload); 651 652 // verify method dispatching 653 Mockito.verify(mObserver).onItemRangeChanged(0, 1, null); 654 Mockito.verify(mObserver).onItemRangeMoved(2, 1, 1); 655 } 656 657 /** 658 * Test8 will trigger multiple items insertion event 659 */ 660 @Test 661 public void testSetItemsMethod8() { 662 663 // initial item list 664 mAdapter.clear(); 665 mItems.add(new AdapterItem(0, "a")); 666 mItems.add(new AdapterItem(1, "b")); 667 mAdapter.clear(); 668 mAdapter.setItems(mItems, mCallbackWithoutPayload); 669 670 // Clear previous items and set a new list of items. 671 mItems.clear(); 672 mItems.add(new AdapterItem(0, "a")); 673 mItems.add(new AdapterItem(1, "b")); 674 mItems.add(new AdapterItem(2, "c")); 675 mItems.add(new AdapterItem(3, "d")); 676 677 // reset mocked object before calling setItems method 678 Mockito.reset(mObserver); 679 mAdapter.setItems(mItems, mCallbackWithoutPayload); 680 681 // verify method dispatching 682 Mockito.verify(mObserver).onItemRangeInserted(2, 2); 683 Mockito.reset(mObserver); 684 } 685 686 687 /** 688 * The following test cases are mainly focused on testing setItems method when we need to 689 * calculate payload 690 * 691 * The payload should only be calculated when two items are same but with different contents. 692 * I.e. the calculate payload method should only be executed when the previous condition is 693 * satisfied. In this test case we use a mocked callback object to verify it and compare the 694 * calculated payload with our expected payload. 695 * 696 * Test 0 will calculate the difference on string member one. 697 */ 698 @Test 699 public void testPayloadCalculation0() { 700 AdapterItem oldItem = new AdapterItem(1, "a", "a"); 701 mItems.add(oldItem); 702 mAdapter.setItems(mItems, mMockedCallback); 703 704 // Create a new item list which contain a new AdapterItem object 705 // test if payload is computed correctly by changing string member 1 706 mItems.clear(); 707 AdapterItem newItem = new AdapterItem(1, "aa", "a"); 708 mItems.add(newItem); 709 710 711 // reset mocked object before calling setItems method 712 Mockito.reset(mObserver); 713 Mockito.reset(mMockedCallback); 714 mAdapter.setItems(mItems, mMockedCallback); 715 716 // Create expected payload manually for verification 717 Bundle expectedPayload0 = new Bundle(); 718 expectedPayload0.putString(STRING_MEMBER_ONE, newItem.getStringMemberOne()); 719 720 // make sure no other event will be triggered in current scenario 721 Mockito.verify(mObserver, never()).onItemRangeInserted(anyInt(), anyInt()); 722 Mockito.verify(mObserver, never()).onItemRangeRemoved(anyInt(), anyInt()); 723 Mockito.verify(mObserver, never()).onItemRangeMoved(anyInt(), anyInt(), anyInt()); 724 725 // Check if getChangePayload is executed as we expected 726 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), eq(null)); 727 Mockito.verify(mMockedCallback).getChangePayload(oldItem, 728 newItem); 729 730 // compare the two bundles by iterating each member 731 Bundle calculatedBundle0 = (Bundle) mMockedCallback.getChangePayload( 732 oldItem, newItem); 733 compareTwoBundles(calculatedBundle0, expectedPayload0); 734 735 } 736 737 /** 738 * Test 1 will calculate the difference on string member two. 739 */ 740 @Test 741 public void testPayloadComputation1() { 742 AdapterItem oldItem = new AdapterItem(1, "a", "a"); 743 mItems.add(oldItem); 744 mAdapter.setItems(mItems, mMockedCallback); 745 746 // Create a new item list which contain a new AdapterItem object 747 // test if payload is computed correctly by changing string member 2 748 mItems.clear(); 749 AdapterItem newItem = new AdapterItem(1, "a", "aa"); 750 mItems.add(newItem); 751 752 // reset mocked object before calling setItems method 753 Mockito.reset(mObserver); 754 Mockito.reset(mMockedCallback); 755 mAdapter.setItems(mItems, mMockedCallback); 756 757 // Create expected payload manually for verification 758 Bundle expectedPayload0 = new Bundle(); 759 expectedPayload0.putString(STRING_MEMBER_TWO, newItem.getStringMemberTwo()); 760 761 // make sure no other event will be triggered in current scenario 762 Mockito.verify(mObserver, never()).onItemRangeInserted(anyInt(), anyInt()); 763 Mockito.verify(mObserver, never()).onItemRangeRemoved(anyInt(), anyInt()); 764 Mockito.verify(mObserver, never()).onItemRangeMoved(anyInt(), anyInt(), anyInt()); 765 766 // Check if getChangePayload is executed as we expected 767 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), eq(null)); 768 Mockito.verify(mMockedCallback).getChangePayload(oldItem, 769 newItem); 770 771 // compare the two bundles by iterating each member 772 Bundle calculatedBundle0 = (Bundle) mMockedCallback.getChangePayload( 773 oldItem, newItem); 774 compareTwoBundles(calculatedBundle0, expectedPayload0); 775 776 } 777 778 /** 779 * Test 1 will calculate the difference on string member one and string member two. 780 */ 781 @Test 782 public void testPayloadComputation2() { 783 AdapterItem oldItem = new AdapterItem(1, "a", "a"); 784 mItems.add(oldItem); 785 mAdapter.setItems(mItems, mMockedCallback); 786 787 // Create a new item list which contain a new AdapterItem object 788 // test if payload is computed correctly by changing string member 1 and string member 2 789 mItems.clear(); 790 AdapterItem newItem = new AdapterItem(1, "aa", "aa"); 791 mItems.add(newItem); 792 793 // reset mocked object before calling setItems method 794 Mockito.reset(mObserver); 795 Mockito.reset(mMockedCallback); 796 mAdapter.setItems(mItems, mMockedCallback); 797 798 // Create expected payload manually for verification 799 Bundle expectedPayload0 = new Bundle(); 800 expectedPayload0.putString(STRING_MEMBER_ONE, newItem.getStringMemberOne()); 801 expectedPayload0.putString(STRING_MEMBER_TWO, newItem.getStringMemberTwo()); 802 803 // make sure no other event will be triggered in current scenario 804 Mockito.verify(mObserver, never()).onItemRangeInserted(anyInt(), anyInt()); 805 Mockito.verify(mObserver, never()).onItemRangeRemoved(anyInt(), anyInt()); 806 Mockito.verify(mObserver, never()).onItemRangeMoved(anyInt(), anyInt(), anyInt()); 807 808 // Check if getChangePayload is executed as we expected 809 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), eq(null)); 810 Mockito.verify(mMockedCallback).getChangePayload(oldItem, 811 newItem); 812 813 // compare the two bundles by iterating each member 814 Bundle calculatedBundle0 = (Bundle) mMockedCallback.getChangePayload( 815 oldItem, newItem); 816 compareTwoBundles(calculatedBundle0, expectedPayload0); 817 818 } 819 820 /** 821 * Test payload computation process under the condition when two items are not the same 822 * based on areItemsTheSame function in DiffUtilCallback 823 */ 824 @Test 825 public void testPayloadComputationNewItem0() { 826 AdapterItem oldItem = new AdapterItem(1, "a", "a"); 827 mItems.add(oldItem); 828 mAdapter.setItems(mItems, mMockedCallback); 829 830 // Create a new item list which contain a new AdapterItem object 831 // The id of the new item is changed, and will be treated as a new item according to the 832 // rule we set in the callback. This test case is to verify the getChangePayload 833 // method still honor the standard we set up to judge new item 834 mItems.clear(); 835 AdapterItem newItem = new AdapterItem(2, "a", "a"); 836 mItems.add(newItem); 837 838 // reset mocked object before calling setItems method 839 Mockito.reset(mObserver); 840 Mockito.reset(mMockedCallback); 841 mAdapter.setItems(mItems, mMockedCallback); 842 843 // Make sure only remove/ insert event will be fired under this circumstance 844 Mockito.verify(mObserver).onItemRangeRemoved(0, 1); 845 Mockito.verify(mObserver).onItemRangeInserted(0, 1); 846 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), any()); 847 Mockito.verify(mMockedCallback, never()).getChangePayload((AdapterItem) any(), 848 (AdapterItem) any()); 849 850 } 851 852 /** 853 * Test payload computation process under the condition when two items are not the same 854 * based on areItemsTheSame function in DiffUtilCallback 855 * 856 * But in test 1 we have changed string member one for sanity check. 857 */ 858 @Test 859 public void testPayloadComputationNewItem1() { 860 AdapterItem oldItem = new AdapterItem(1, "a", "a"); 861 mItems.add(oldItem); 862 mAdapter.setItems(mItems, mMockedCallback); 863 864 // Create a new item list which contain a new AdapterItem object 865 // The id of the new item is changed, and will be treated as a new item according to the 866 // rule we set in the callback. This test case is to verify the getChangePayload 867 // method still honor the standard we set up to judge new item 868 mItems.clear(); 869 AdapterItem newItem = new AdapterItem(2, "aa", "a"); 870 mItems.add(newItem); 871 872 // reset mocked object before calling setItems method 873 Mockito.reset(mObserver); 874 Mockito.reset(mMockedCallback); 875 mAdapter.setItems(mItems, mMockedCallback); 876 877 // Make sure only remove/ insert event will be fired under this circumstance 878 Mockito.verify(mObserver).onItemRangeRemoved(0, 1); 879 Mockito.verify(mObserver).onItemRangeInserted(0, 1); 880 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), any()); 881 Mockito.verify(mMockedCallback, never()).getChangePayload((AdapterItem) any(), 882 (AdapterItem) any()); 883 884 } 885 886 /** 887 * Test payload computation process under the condition when two items are not the same 888 * based on areItemsTheSame function in DiffUtilCallback 889 * 890 * But in test 2 we have changed string member two for sanity check. 891 */ 892 @Test 893 public void testPayloadComputationNewItem2() { 894 AdapterItem oldItem = new AdapterItem(1, "a", "a"); 895 mItems.add(oldItem); 896 mAdapter.setItems(mItems, mMockedCallback); 897 898 // Create a new item list which contain a new AdapterItem object 899 // The id of the new item is changed, and will be treated as a new item according to the 900 // rule we set in the callback. This test case is to verify the getChangePayload 901 // method still honor the standard we set up to judge new item 902 mItems.clear(); 903 AdapterItem newItem = new AdapterItem(2, "a", "aa"); 904 mItems.add(newItem); 905 906 // reset mocked object before calling setItems method 907 Mockito.reset(mObserver); 908 Mockito.reset(mMockedCallback); 909 mAdapter.setItems(mItems, mMockedCallback); 910 911 // Make sure only remove/ insert event will be fired under this circumstance 912 Mockito.verify(mObserver).onItemRangeRemoved(0, 1); 913 Mockito.verify(mObserver).onItemRangeInserted(0, 1); 914 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), any()); 915 Mockito.verify(mMockedCallback, never()).getChangePayload((AdapterItem) any(), 916 (AdapterItem) any()); 917 918 } 919 920 /** 921 * Test payload computation process under the condition when two items are not the same 922 * based on areItemsTheSame function in DiffUtilCallback 923 * 924 * But in test 3 we have changed string member one and string member two for sanity check. 925 */ 926 @Test 927 public void testPayloadComputationNewItem3() { 928 AdapterItem oldItem = new AdapterItem(1, "a", "a"); 929 mItems.add(oldItem); 930 mAdapter.setItems(mItems, mMockedCallback); 931 932 // Create a new item list which contain a new AdapterItem object 933 // The id of the new item is changed, and will be treated as a new item according to the 934 // rule we set in the callback. This test case is to verify the getChangePayload 935 // method still honor the standard we set up to judge new item 936 mItems.clear(); 937 AdapterItem newItem = new AdapterItem(2, "aa", "aa"); 938 mItems.add(newItem); 939 940 // reset mocked object before calling setItems method 941 Mockito.reset(mObserver); 942 Mockito.reset(mMockedCallback); 943 mAdapter.setItems(mItems, mMockedCallback); 944 945 // Make sure only remove/ insert event will be fired under this circumstance 946 Mockito.verify(mObserver).onItemRangeRemoved(0, 1); 947 Mockito.verify(mObserver).onItemRangeInserted(0, 1); 948 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), any()); 949 Mockito.verify(mMockedCallback, never()).getChangePayload((AdapterItem) any(), 950 (AdapterItem) any()); 951 } 952 953 /** 954 * Test payload computation process under the condition when two items have the same content 955 * based on areContentsTheSame function in DiffUtilCallback 956 */ 957 @Test 958 public void testPayloadComputationSameContent() { 959 AdapterItem oldItem = new AdapterItem(1, "a", "a", "a"); 960 mItems.add(oldItem); 961 mAdapter.setItems(mItems, mMockedCallback); 962 963 // Create a new item list which contain a new AdapterItem object 964 // The non-related string member of the new item is changed, but the two items are still 965 // the same as well as the item's content according to the rule we set in the callback. 966 // This test case is to verify the getChangePayload method still honor the standard 967 // we set up to determine if a new object is 1. a new item 2. has the same content as the 968 // previous one 969 mItems.clear(); 970 AdapterItem newItem = new AdapterItem(1, "a", "a", "aa"); 971 mItems.add(newItem); 972 973 // reset mocked object before calling setItems method 974 Mockito.reset(mObserver); 975 Mockito.reset(mMockedCallback); 976 mAdapter.setItems(mItems, mMockedCallback); 977 978 // Make sure no even will be fired up in this circumstance 979 Mockito.verify(mObserver, never()).onItemRangeRemoved(anyInt(), anyInt()); 980 Mockito.verify(mObserver, never()).onItemRangeInserted(anyInt(), anyInt()); 981 Mockito.verify(mObserver, never()).onItemRangeChanged(anyInt(), anyInt(), any()); 982 Mockito.verify(mMockedCallback, never()).getChangePayload((AdapterItem) any(), 983 (AdapterItem) any()); 984 } 985 986 987 /** 988 * This test case is targeted at real ui testing. I.e. making sure when the change of adapter's 989 * items will trigger the rebinding of view holder with payload. That's item presenter's 990 * onBindViewHolder method with payload supporting. 991 * 992 */ 993 @Test 994 public void testPresenterAndItemBridgeAdapter() { 995 // data set one 996 final List<AdapterItem> dataSetOne = new ArrayList<>(); 997 AdapterItem dataSetOne0 = new AdapterItem(1, "a"); 998 AdapterItem dataSetOne1 = new AdapterItem(2, "b"); 999 AdapterItem dataSetOne2 = new AdapterItem(3, "c"); 1000 AdapterItem dataSetOne3 = new AdapterItem(4, "d"); 1001 AdapterItem dataSetOne4 = new AdapterItem(5, "3"); 1002 dataSetOne.add(dataSetOne0); 1003 dataSetOne.add(dataSetOne1); 1004 dataSetOne.add(dataSetOne2); 1005 dataSetOne.add(dataSetOne3); 1006 dataSetOne.add(dataSetOne4); 1007 1008 // data set two 1009 final List<AdapterItem> dataSetTwo = new ArrayList<>(); 1010 AdapterItem dataSetTwo0 = new AdapterItem(1, "aa"); 1011 AdapterItem dataSetTwo1 = new AdapterItem(2, "bb"); 1012 AdapterItem dataSetTwo2 = new AdapterItem(3, "cc"); 1013 AdapterItem dataSetTwo3 = new AdapterItem(4, "dd"); 1014 AdapterItem dataSetTwo4 = new AdapterItem(5, "ee"); 1015 dataSetTwo.add(dataSetTwo0); 1016 dataSetTwo.add(dataSetTwo1); 1017 dataSetTwo.add(dataSetTwo2); 1018 dataSetTwo.add(dataSetTwo3); 1019 dataSetTwo.add(dataSetTwo4); 1020 1021 ((ArrayObjectAdapter) mRow.getAdapter()).addAll(0, dataSetOne); 1022 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 1023 @Override 1024 public void run() { 1025 1026 // obtain frame layout through context. 1027 final ViewGroup parent = new FrameLayout(mContext); 1028 1029 // create view holder and obtain the view object from view holder 1030 // add view object to our layout 1031 Presenter.ViewHolder containerVh = mListRowPresenter.onCreateViewHolder(parent); 1032 parent.addView(containerVh.view, 1000, 1000); 1033 1034 // set rows adapter and add row to that adapter 1035 mRowsAdapter = new ArrayObjectAdapter(); 1036 mRowsAdapter.add(mRow); 1037 1038 // use the presenter to bind row view holder explicitly. So the itemBridgeAdapter 1039 // will be connected to the adapter inside of the listRow successfully. 1040 mListVh = (ListRowPresenter.ViewHolder) mListRowPresenter.getRowViewHolder( 1041 containerVh); 1042 mListRowPresenter.onBindRowViewHolder(mListVh, mRow); 1043 1044 // layout the list row in recycler view 1045 runRecyclerViewLayout(); 1046 1047 // reset mocked presenter 1048 Mockito.reset(mListRowPresenter); 1049 Mockito.reset(mAdapterItemPresenter); 1050 1051 // calling setItem's method to trigger the diff computation 1052 ((ArrayObjectAdapter) mRow.getAdapter()).setItems(dataSetTwo, 1053 new DiffCallbackPayloadTesting()); 1054 1055 // re-layout the recycler view to trigger getViewForPosition event 1056 runRecyclerViewLayout(); 1057 1058 // verify method execution 1059 Mockito.verify(mAdapterItemPresenter, never()).onBindViewHolder( 1060 (RowPresenter.ViewHolder) any(), (Object) any()); 1061 Mockito.verify(mAdapterItemPresenter, atLeast(5)).onBindViewHolder( 1062 (RowPresenter.ViewHolder) any(), (Object) any(), (List<Object>) any()); 1063 } 1064 }); 1065 } 1066 1067 /** 1068 * Helper function to layout recycler view 1069 * So the recycler view will execute the getView() method then the onBindViewHolder() method 1070 * from presenter will be executed 1071 */ 1072 private void runRecyclerViewLayout() { 1073 mListVh.view.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), 1074 View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY)); 1075 mListVh.view.layout(0, 0, 1000, 1000); 1076 } 1077 1078 /** 1079 * Helper function to compare two bundles through iterating the fields. 1080 * 1081 * @param bundle1 bundle 1 1082 * @param bundle2 bundle 2 1083 */ 1084 private void compareTwoBundles(Bundle bundle1, Bundle bundle2) { 1085 assertEquals(bundle1.getInt(ID), bundle2.getInt(ID)); 1086 assertEquals(bundle1.getString(STRING_MEMBER_ONE), bundle2.getString( 1087 STRING_MEMBER_ONE)); 1088 assertEquals(bundle1.getString(STRING_MEMBER_TWO), bundle2.getString( 1089 STRING_MEMBER_TWO)); 1090 assertEquals(bundle1.getString(NOT_RELATED_STRING_MEMBER), 1091 bundle2.getString(NOT_RELATED_STRING_MEMBER)); 1092 } 1093 1094 /** 1095 * Helper function to test the content in adapter 1096 */ 1097 private static void assertAdapterContent(ObjectAdapter adapter, Object[] data) { 1098 assertEquals(adapter.size(), data.length); 1099 for (int i = 0; i < adapter.size(); i++) { 1100 assertEquals(adapter.get(i), data[i]); 1101 } 1102 } 1103} 1104