1 /* 2 * Copyright (C) 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.globalsearch; 18 19 import junit.framework.TestCase; 20 import junit.framework.Assert; 21 import android.content.ComponentName; 22 import android.test.MoreAsserts; 23 import com.google.android.collect.Sets; 24 import com.google.android.collect.Lists; 25 26 import static com.android.globalsearch.SourceSuggestionBacker.SourceStat; 27 28 import java.util.List; 29 import java.util.ArrayList; 30 import java.util.HashSet; 31 import java.util.Arrays; 32 33 /** 34 * Tests {@link SourceSuggestionBacker} 35 * 36 * see the {@link #setUp()} method for the default setup of the backer that is used by most 37 * test cases. 38 */ 39 public class SourceSuggestionBackerTest extends TestCase 40 implements SourceSuggestionBacker.MoreExpanderFactory, 41 SourceSuggestionBacker.CorpusResultFactory { 42 43 private ComponentName mName1; 44 private ComponentName mName2; 45 private ComponentName mName3; 46 private TestSuggestionSource mSource1; 47 private TestSuggestionSource mSource2; 48 private TestSuggestionSource mSource3; 49 private TestBacker mBacker; 50 private static final int MAX_PROMOTED_SHOWING = 6; 51 private static final long NOW = 700L; 52 private static final long DEADLINE = 2000L; 53 private SuggestionData mShortcut1; 54 private SuggestionData mGoToWebsite; 55 private SuggestionData mSearchTheWeb; 56 private SuggestionData mMoreNotExpanded; 57 private SuggestionData mMoreExpanded; 58 private static final String SOURCE1_LABEL = "source1 label"; 59 private static final String SOURCE2_LABEL = "source2 label"; 60 private static final String SOURCE3_LABEL = "source3 label"; 61 62 63 @Override setUp()64 protected void setUp() throws Exception { 65 super.setUp(); 66 67 mName1 = new ComponentName( 68 "com.android.globalsearch", "com.android.globalsearch.One"); 69 70 mName2 = new ComponentName( 71 "com.android.globalsearch", "com.android.globalsearch.Two"); 72 73 mName3 = new ComponentName( 74 "com.android.globalsearch", "com.android.globalsearch.Three"); 75 76 mSource1 = new TestSuggestionSource.Builder() 77 .setComponent(mName1) 78 .setLabel(SOURCE1_LABEL) 79 .create(); 80 81 mSource2 = new TestSuggestionSource.Builder() 82 .setComponent(mName2) 83 .setLabel(SOURCE2_LABEL) 84 .create(); 85 86 mSource3 = new TestSuggestionSource.Builder() 87 .setComponent(mName3) 88 .setLabel(SOURCE3_LABEL) 89 .create(); 90 91 mShortcut1 = new SuggestionData.Builder(mName1) 92 .title("shortcut") 93 .description("description") 94 .shortcutId("shortcutid") 95 .intentAction("action.VIEW") 96 .intentData("shorctut1") 97 .build(); 98 99 // Normally we pass null in for this value; we only use this value to 100 // explicitly test the "go to website" functionality. 101 mGoToWebsite = new SuggestionData.Builder(mName1) 102 .title("go to website") 103 .description("google.com") 104 .build(); 105 106 mSearchTheWeb = new SuggestionData.Builder(mName1) 107 .title("search the web for 'yo'") 108 .description("description") 109 .build(); 110 111 mBacker = new TestBacker( 112 "query", 113 Lists.newArrayList(mShortcut1), 114 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3), 115 Sets.newHashSet(mName1, mName2), // promoted sources 116 mSource1, // source1 is the web souce 117 Lists.<SuggestionResult>newArrayList(), 118 null, // no "go to website" suggestion 119 mSearchTheWeb, // the "search the web" suggestion 120 MAX_PROMOTED_SHOWING, 121 DEADLINE, 122 this, /** more factory points to {@link #getMoreEntry} */ 123 this); /** corpus factory points to {@link #getCorpusEntry} */ 124 125 mMoreNotExpanded = new SuggestionData.Builder(mName1) 126 .title("more unexpanded") 127 .build(); 128 129 mMoreExpanded = new SuggestionData.Builder(mName1) 130 .title("more expanded") 131 .build(); 132 133 mBacker.setNow(NOW); 134 } 135 136 /** {@inheritDoc} */ getMoreEntry( boolean expanded, List<SourceSuggestionBacker.SourceStat> sourceStats)137 public SuggestionData getMoreEntry( 138 boolean expanded, 139 List<SourceSuggestionBacker.SourceStat> sourceStats) { 140 return expanded ? mMoreExpanded : mMoreNotExpanded; 141 } 142 143 /** {@inheritDoc} */ getCorpusEntry( String query, SourceSuggestionBacker.SourceStat sourceStat)144 public SuggestionData getCorpusEntry( 145 String query, SourceSuggestionBacker.SourceStat sourceStat) { 146 return makeCorpusEntry( 147 sourceStat.getLabel(), 148 sourceStat.getResponseStatus(), 149 sourceStat.getNumResults()); 150 } 151 152 testNoResultsReported()153 public void testNoResultsReported() { 154 155 assertContentsInOrder( 156 "should only be shortcuts before deadline.", 157 getSnapshotFromBacker(false), 158 mShortcut1); 159 160 assertContentsInOrder( 161 "should only be shortcuts before deadline.", 162 getSnapshotFromBacker(true), 163 mShortcut1); 164 165 mBacker.setNow(NOW + DEADLINE); 166 167 assertContentsInOrder( 168 "after deadline should see 'search the web' and 'more' entry.", 169 getSnapshotFromBacker(false), 170 mShortcut1, 171 mSearchTheWeb, 172 mMoreNotExpanded); 173 174 assertContentsInOrder( 175 "after deadline (expanded) should see 'search the web' and 'more' entries.", 176 getSnapshotFromBacker(true), 177 mShortcut1, 178 mSearchTheWeb, 179 mMoreExpanded, 180 makeCorpusEntry(SOURCE1_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0), 181 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0), 182 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0)); 183 } 184 testSomeResultsReported()185 public void testSomeResultsReported() { 186 187 // source 1 reports back 4 results 188 mBacker.addSourceResults( 189 new SuggestionResult(mSource1, Lists.newArrayList( 190 makeSourceResult(mName1, 0), 191 makeSourceResult(mName1, 1), 192 makeSourceResult(mName1, 2), 193 makeSourceResult(mName1, 3) 194 ))); 195 196 assertContentsInOrder( 197 "before deadline, should show shortcuts and chunks of promoted sources.", 198 getSnapshotFromBacker(false), 199 mShortcut1, 200 makeSourceResult(mName1, 0), 201 makeSourceResult(mName1, 1)); 202 203 mBacker.setNow(NOW + DEADLINE); 204 205 // source 3 is in progress 206 mBacker.reportSourceStarted(mName3); 207 208 assertContentsInOrder( 209 "after deadline(expanded), should show shortcuts, chunks of promoted sources, " + 210 "rest of promoted slots filled, and more.", 211 getSnapshotFromBacker(true), 212 mShortcut1, 213 makeSourceResult(mName1, 0), 214 makeSourceResult(mName1, 1), 215 makeSourceResult(mName1, 2), 216 makeSourceResult(mName1, 3), 217 mSearchTheWeb, 218 mMoreExpanded, 219 // no "more" result for source 1 since we've displayed all of its entries now 220 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0), 221 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_IN_PROGRESS, 0)); 222 } 223 testPromotedSourceRespondsAfterDeadline()224 public void testPromotedSourceRespondsAfterDeadline() { 225 mBacker.addSourceResults( 226 new SuggestionResult(mSource1, Lists.newArrayList( 227 makeSourceResult(mName1, 0), 228 makeSourceResult(mName1, 1)))); 229 230 mBacker.setNow(NOW + DEADLINE); 231 232 assertContentsInOrder( 233 "after deadline pending promoted source should be under 'more'.", 234 getSnapshotFromBacker(true), 235 mShortcut1, 236 makeSourceResult(mName1, 0), 237 makeSourceResult(mName1, 1), 238 mSearchTheWeb, 239 mMoreExpanded, 240 // source 2 hasn't responded yet 241 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0), 242 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0)); 243 244 mBacker.addSourceResults( 245 new SuggestionResult(mSource2, Lists.newArrayList( 246 makeSourceResult(mName2, 0), 247 makeSourceResult(mName2, 1)))); 248 249 assertContentsInOrder( 250 "after deadline late reporting promoted source should be under 'more'.", 251 getSnapshotFromBacker(true), 252 mShortcut1, 253 makeSourceResult(mName1, 0), 254 makeSourceResult(mName1, 1), 255 mSearchTheWeb, 256 mMoreExpanded, 257 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 2), 258 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0)); 259 } 260 testZeroReportingSources()261 public void testZeroReportingSources() { 262 263 assertFalse("reporting zero results before ever being shown should not require updating.", 264 mBacker.addSourceResults(new SuggestionResult(mSource1))); 265 266 assertContentsInOrder( 267 "zero reporting before deadline.", 268 getSnapshotFromBacker(true), 269 mShortcut1); 270 271 mBacker.setNow(NOW + DEADLINE); 272 273 assertContentsInOrder( 274 "zero reporting before deadline, viewing after.", 275 getSnapshotFromBacker(true), 276 mShortcut1, 277 mSearchTheWeb, 278 mMoreExpanded, 279 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0), 280 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0)); 281 282 // source 2 reports after deadline 283 assertTrue("reporting zero results after being shown should require updating.", 284 mBacker.addSourceResults(new SuggestionResult(mSource2))); 285 286 assertContentsInOrder( 287 "zero reporting after deadline.", 288 getSnapshotFromBacker(true), 289 mShortcut1, 290 mSearchTheWeb, 291 mMoreExpanded, 292 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 0), 293 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0)); 294 295 mBacker.addSourceResults( 296 new SuggestionResult(mSource3, Lists.newArrayList( 297 makeSourceResult(mName3, 0), 298 makeSourceResult(mName3, 1)))); 299 300 assertContentsInOrder( 301 "last source reported after deadline.", 302 getSnapshotFromBacker(true), 303 mShortcut1, 304 mSearchTheWeb, 305 mMoreExpanded, 306 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 0), 307 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 2)); 308 } 309 testSourceReportsAfterDeadlineWithResults()310 public void testSourceReportsAfterDeadlineWithResults() { 311 mBacker = new TestBacker( 312 "query", 313 Lists.<SuggestionData>newArrayList(), // no shortcuts 314 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3), 315 Sets.newHashSet(mName1, mName2, mName3), // all 3 are promoted sources 316 mSource1, 317 Lists.<SuggestionResult>newArrayList(), 318 null, 319 mSearchTheWeb, 320 MAX_PROMOTED_SHOWING, 321 DEADLINE, 322 this, 323 this); 324 325 mBacker.addSourceResults( 326 new SuggestionResult(mSource1, Lists.newArrayList( 327 makeSourceResult(mName1, 0), 328 makeSourceResult(mName1, 1), 329 makeSourceResult(mName1, 2) 330 ))); 331 332 mBacker.addSourceResults( 333 new SuggestionResult(mSource2, Lists.newArrayList( 334 makeSourceResult(mName2, 0), 335 makeSourceResult(mName2, 1), 336 makeSourceResult(mName2, 2) 337 ))); 338 339 mBacker.setNow(NOW + DEADLINE); 340 341 assertContentsInOrder( 342 "after deadline passes, we mix in the remaining promoted slots among the " + 343 "promoted sources that have responded.", 344 getSnapshotFromBacker(true), 345 makeSourceResult(mName1, 0), 346 makeSourceResult(mName1, 1), 347 makeSourceResult(mName2, 0), 348 makeSourceResult(mName2, 1), 349 makeSourceResult(mName1, 2), // remaining space (now that deadline has passed) 350 makeSourceResult(mName2, 2), 351 mSearchTheWeb, 352 mMoreExpanded, 353 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0) 354 ); 355 356 mBacker.addSourceResults( 357 new SuggestionResult(mSource3, Lists.newArrayList( 358 makeSourceResult(mName3, 0), 359 makeSourceResult(mName3, 1), 360 makeSourceResult(mName3, 2) 361 ))); 362 363 assertContentsInOrder( 364 "promoted source reported after deadline, results should remain stable (even " + 365 "though this is not the optimal order if we had known the third promoted " + 366 "source was going to miss the deadline).", 367 getSnapshotFromBacker(true), 368 makeSourceResult(mName1, 0), 369 makeSourceResult(mName1, 1), 370 makeSourceResult(mName2, 0), 371 makeSourceResult(mName2, 1), 372 makeSourceResult(mName1, 2), 373 makeSourceResult(mName2, 2), 374 mSearchTheWeb, 375 mMoreExpanded, 376 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 3) 377 ); 378 } 379 testFillSpaceLargerThanChunkSizeAfterDeadline()380 public void testFillSpaceLargerThanChunkSizeAfterDeadline() { 381 mBacker = new TestBacker( 382 "query", 383 Lists.<SuggestionData>newArrayList(), // no shortcuts 384 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3), 385 Sets.newHashSet(mName1, mName2, mName3), // all 3 are promoted sources 386 mSource1, 387 Lists.<SuggestionResult>newArrayList(), 388 null, 389 mSearchTheWeb, 390 MAX_PROMOTED_SHOWING, 391 DEADLINE, 392 this, 393 this); 394 395 mBacker.addSourceResults( 396 new SuggestionResult(mSource1, Lists.newArrayList( 397 makeSourceResult(mName1, 0), 398 makeSourceResult(mName1, 1), 399 makeSourceResult(mName1, 2), 400 makeSourceResult(mName1, 3), 401 makeSourceResult(mName1, 4), 402 makeSourceResult(mName1, 5) 403 ))); 404 405 mBacker.addSourceResults( 406 new SuggestionResult(mSource2, Lists.newArrayList( 407 makeSourceResult(mName2, 0) 408 ))); 409 410 mBacker.setNow(NOW + DEADLINE); 411 412 mBacker.addSourceResults( 413 new SuggestionResult(mSource3, Lists.newArrayList( 414 makeSourceResult(mName3, 0), 415 makeSourceResult(mName3, 1), 416 makeSourceResult(mName3, 2), 417 makeSourceResult(mName3, 3), 418 makeSourceResult(mName3, 4), 419 makeSourceResult(mName3, 5) 420 ))); 421 422 assertContentsInOrder( 423 "after deadline has passed, promoted sources that have reported should fill in " + 424 "the remaining slots", 425 getSnapshotFromBacker(true), 426 // chunk 1 427 makeSourceResult(mName1, 0), 428 makeSourceResult(mName1, 1), 429 // chunk 2 430 makeSourceResult(mName2, 0), 431 // remaining space 432 makeSourceResult(mName1, 2), 433 makeSourceResult(mName1, 3), 434 makeSourceResult(mName1, 4), 435 mSearchTheWeb, 436 mMoreExpanded, 437 makeCorpusEntry(SOURCE1_LABEL, SourceStat.RESPONSE_FINISHED, 1), // 1 remaining result 438 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 6)); // reported after deadline 439 } 440 testAllResultsReported()441 public void testAllResultsReported() { 442 443 // each one reports 4 results 444 mBacker.addSourceResults( 445 new SuggestionResult(mSource1, Lists.newArrayList( 446 makeSourceResult(mName1, 0), 447 makeSourceResult(mName1, 1), 448 makeSourceResult(mName1, 2), 449 makeSourceResult(mName1, 3) 450 ))); 451 mBacker.addSourceResults( 452 new SuggestionResult(mSource2, Lists.newArrayList( 453 makeSourceResult(mName2, 0), 454 makeSourceResult(mName2, 1), 455 makeSourceResult(mName2, 2), 456 makeSourceResult(mName2, 3) 457 ))); 458 mBacker.addSourceResults( 459 new SuggestionResult(mSource3, Lists.newArrayList( 460 makeSourceResult(mName3, 0), 461 makeSourceResult(mName3, 1), 462 makeSourceResult(mName3, 2), 463 makeSourceResult(mName3, 3) 464 ))); 465 466 assertContentsInOrder( 467 "all responded.", 468 getSnapshotFromBacker(false), 469 mShortcut1, 470 makeSourceResult(mName1, 0), 471 makeSourceResult(mName1, 1), 472 makeSourceResult(mName2, 0), 473 makeSourceResult(mName2, 1), 474 makeSourceResult(mName1, 2), // remaining slots (source 3 is not promoted) 475 mSearchTheWeb, 476 mMoreNotExpanded); 477 478 assertContentsInOrder( 479 "all responded (expanded).", 480 getSnapshotFromBacker(true), 481 mShortcut1, 482 makeSourceResult(mName1, 0), 483 makeSourceResult(mName1, 1), 484 makeSourceResult(mName2, 0), 485 makeSourceResult(mName2, 1), 486 makeSourceResult(mName1, 2), 487 mSearchTheWeb, 488 mMoreExpanded, 489 makeCorpusEntry(SOURCE1_LABEL, SourceStat.RESPONSE_FINISHED, 1), // 1 remaining 490 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 2), // 2 remaining 491 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 4)); 492 } 493 testNoWebSource()494 public void testNoWebSource() { 495 mBacker = new TestBacker( 496 "query", 497 Lists.newArrayList(mShortcut1), 498 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3), 499 Sets.newHashSet(mName1, mName2), // promoted sources 500 null, // NO WEB SOURCE 501 Lists.<SuggestionResult>newArrayList(), 502 null, // no "go to website" suggestion 503 mSearchTheWeb, // the "search the web" suggestion 504 MAX_PROMOTED_SHOWING, 505 DEADLINE, 506 this, /** more factory points to {@link #getMoreEntry} */ 507 this); /** corpus factory points to {@link #getCorpusEntry} */ 508 509 510 mBacker.addSourceResults( 511 new SuggestionResult(mSource1, Lists.newArrayList( 512 makeSourceResult(mName1, 0), 513 makeSourceResult(mName1, 1), 514 makeSourceResult(mName1, 2), 515 makeSourceResult(mName1, 3) 516 ))); 517 518 assertContentsInOrder( 519 "expecting business as usual even though there is no web source enabled.", 520 getSnapshotFromBacker(false), 521 mShortcut1, 522 makeSourceResult(mName1, 0), 523 makeSourceResult(mName1, 1)); 524 } 525 testDuplicatesOfShortcut()526 public void testDuplicatesOfShortcut() { 527 528 // four results from source 1, the first of which is a dupe of the shortcut 529 mBacker.addSourceResults( 530 new SuggestionResult(mSource1, Lists.newArrayList( 531 mShortcut1, 532 makeSourceResult(mName1, 1), 533 makeSourceResult(mName1, 2), 534 makeSourceResult(mName1, 3) 535 ))); 536 537 // before deadline, not all have reported, so just one chunk from source 1 538 assertContentsInOrder( 539 "expecting duplicate to be removed.", 540 getSnapshotFromBacker(false), 541 mShortcut1, 542 makeSourceResult(mName1, 1), 543 makeSourceResult(mName1, 2)); 544 } 545 testDuplicatesBetweenSources()546 public void testDuplicatesBetweenSources() { 547 // two different suggestions for facebook with the same intent action and intent data 548 final SuggestionData facebook1 = new SuggestionData.Builder(mName1) 549 .title("facebook") 550 .intentAction("action.VIEW") 551 .intentData("http://www.facebook.com") 552 .build(); 553 final SuggestionData facebook2 = new SuggestionData.Builder(mName2) 554 .title("facebook home") 555 .intentAction("action.VIEW") 556 .intentData("http://www.facebook.com") 557 .build(); 558 559 mBacker.addSourceResults( 560 new SuggestionResult(mSource1, Lists.newArrayList( 561 facebook1 562 ))); 563 mBacker.addSourceResults( 564 new SuggestionResult(mSource2, Lists.newArrayList( 565 facebook2 566 ))); 567 568 assertContentsInOrder( 569 "expecting duplicate to be removed.", 570 getSnapshotFromBacker(false), 571 mShortcut1, 572 facebook1, 573 mSearchTheWeb, 574 mMoreNotExpanded); 575 } 576 577 /** 578 * When there are duplicates removed, we still want to fill the valuable slots with more 579 * information. 580 */ testDuplicatesRemovedSlotsStillFilled()581 public void testDuplicatesRemovedSlotsStillFilled() { 582 // two different suggestions for facebook with the same intent action and intent data 583 final SuggestionData facebook1 = new SuggestionData.Builder(mName1) 584 .title("facebook") 585 .intentAction("action.VIEW") 586 .intentData("http://www.facebook.com") 587 .build(); 588 final SuggestionData facebook2 = new SuggestionData.Builder(mName2) 589 .title("facebook home") 590 .intentAction("action.VIEW") 591 .intentData("http://www.facebook.com") 592 .build(); 593 594 mBacker.addSourceResults( 595 new SuggestionResult(mSource1, Lists.newArrayList( 596 facebook1, 597 makeSourceResult(mName1, 1), 598 makeSourceResult(mName1, 2) 599 ))); 600 mBacker.addSourceResults( 601 new SuggestionResult(mSource2, Lists.newArrayList( 602 facebook2, 603 makeSourceResult(mName2, 1), 604 makeSourceResult(mName2, 2), 605 makeSourceResult(mName2, 3) 606 ))); 607 608 assertContentsInOrder( 609 "after duplicate is removed, expecting all 6 promoted slots to still be filled.", 610 getSnapshotFromBacker(false), 611 mShortcut1, 612 // chunk 1 613 facebook1, 614 makeSourceResult(mName1, 1), 615 // chunk 2 616 makeSourceResult(mName2, 1), 617 makeSourceResult(mName2, 2), 618 // remainder 619 makeSourceResult(mName1, 2), 620 mSearchTheWeb, 621 mMoreNotExpanded); 622 } 623 testShortcutsOnly()624 public void testShortcutsOnly() { 625 mBacker = new TestBacker( 626 "query", 627 Lists.newArrayList(mShortcut1), 628 Lists.<SuggestionSource>newArrayList(), // no sources 629 Sets.<ComponentName>newHashSet(), 630 mSource1, 631 Lists.<SuggestionResult>newArrayList(), 632 null, 633 mSearchTheWeb, 634 MAX_PROMOTED_SHOWING, 635 DEADLINE, 636 this, 637 this); 638 mBacker.setNow(NOW); 639 640 assertContentsInOrder( 641 "shortcuts only.", 642 getSnapshotFromBacker(false), 643 mShortcut1); 644 645 assertContentsInOrder( 646 "shortcuts only.", 647 getSnapshotFromBacker(true), 648 mShortcut1); 649 } 650 testAllSourcesPromotedResponded_resultsFitInPromotedSlots()651 public void testAllSourcesPromotedResponded_resultsFitInPromotedSlots() { 652 mBacker = new TestBacker( 653 "query", 654 Lists.newArrayList(mShortcut1), 655 Lists.<SuggestionSource>newArrayList(mSource1, mSource2), 656 Sets.<ComponentName>newHashSet(mName1, mName2), // every source is promoted 657 mSource1, 658 Lists.<SuggestionResult>newArrayList(), 659 null, 660 mSearchTheWeb, 661 MAX_PROMOTED_SHOWING, 662 DEADLINE, 663 this, 664 this); 665 mBacker.setNow(NOW); 666 667 mBacker.addSourceResults( 668 new SuggestionResult(mSource1, Lists.newArrayList( 669 makeSourceResult(mName1, 0), 670 makeSourceResult(mName1, 1)))); 671 mBacker.addSourceResults( 672 new SuggestionResult(mSource2, Lists.newArrayList( 673 makeSourceResult(mName2, 0), 674 makeSourceResult(mName2, 1)))); 675 676 assertContentsInOrder( 677 "should not show 'more' entries if all results fit, and there are no unpromoted " + 678 "sources.", 679 getSnapshotFromBacker(true), 680 mShortcut1, 681 makeSourceResult(mName1, 0), 682 makeSourceResult(mName1, 1), 683 makeSourceResult(mName2, 0), 684 makeSourceResult(mName2, 1), 685 mSearchTheWeb); 686 } 687 testCachedSourceResults()688 public void testCachedSourceResults() { 689 690 final ArrayList<SuggestionResult> cached = Lists.newArrayList( 691 new SuggestionResult(mSource1, Lists.newArrayList( 692 makeSourceResult(mName1, 0), 693 makeSourceResult(mName1, 1))), 694 new SuggestionResult(mSource2, Lists.newArrayList( 695 makeSourceResult(mName2, 0), 696 makeSourceResult(mName2, 1))), 697 new SuggestionResult(mSource3, Lists.newArrayList( 698 makeSourceResult(mName3, 0), 699 makeSourceResult(mName3, 1)))); 700 701 mBacker = new TestBacker( 702 "query", 703 Lists.newArrayList(mShortcut1), 704 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3), 705 Sets.<ComponentName>newHashSet(mName1, mName2), // promoted sources 706 mSource1, 707 cached, 708 null, 709 mSearchTheWeb, 710 MAX_PROMOTED_SHOWING, 711 DEADLINE, 712 this, 713 this); 714 715 assertContentsInOrder( 716 "three cached sources, 2 promoted, one not.", 717 getSnapshotFromBacker(true), 718 mShortcut1, 719 makeSourceResult(mName1, 0), 720 makeSourceResult(mName1, 1), 721 makeSourceResult(mName2, 0), 722 makeSourceResult(mName2, 1), 723 mSearchTheWeb, 724 mMoreExpanded, 725 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 2)); 726 } 727 testGoToWebsiteSuggestion()728 public void testGoToWebsiteSuggestion() { 729 // Recreate the backer, this time with the "go to website" suggestion passed in. 730 // Then check that the backer correctly shows the suggestion even when there are 731 // lots of other results. 732 mBacker = new TestBacker( 733 "query", 734 Lists.newArrayList(mShortcut1), 735 Lists.<SuggestionSource>newArrayList(mSource1, mSource2, mSource3), 736 Sets.newHashSet(mName1, mName2), // promoted sources 737 mSource1, 738 Lists.<SuggestionResult>newArrayList(), 739 mGoToWebsite, 740 mSearchTheWeb, 741 MAX_PROMOTED_SHOWING, 742 DEADLINE, 743 this, 744 this); 745 746 // each one reports 4 results 747 mBacker.addSourceResults( 748 new SuggestionResult(mSource1, Lists.newArrayList( 749 makeSourceResult(mName1, 0), 750 makeSourceResult(mName1, 1), 751 makeSourceResult(mName1, 2), 752 makeSourceResult(mName1, 3) 753 ))); 754 mBacker.addSourceResults( 755 new SuggestionResult(mSource2, Lists.newArrayList( 756 makeSourceResult(mName2, 0), 757 makeSourceResult(mName2, 1), 758 makeSourceResult(mName2, 2), 759 makeSourceResult(mName2, 3) 760 ))); 761 mBacker.addSourceResults( 762 new SuggestionResult(mSource3, Lists.newArrayList( 763 makeSourceResult(mName3, 0), 764 makeSourceResult(mName3, 1), 765 makeSourceResult(mName3, 2), 766 makeSourceResult(mName3, 3) 767 ))); 768 769 assertContentsInOrder( 770 "first suggestion should be go to website suggestion", 771 getSnapshotFromBacker(false), 772 mGoToWebsite, 773 mShortcut1, 774 makeSourceResult(mName1, 0), 775 makeSourceResult(mName1, 1), 776 makeSourceResult(mName2, 0), 777 makeSourceResult(mName2, 1), 778 makeSourceResult(mName1, 2), // remaining slots (source 3 is not promoted) 779 mSearchTheWeb, 780 mMoreNotExpanded); 781 } 782 testPinToBottomSuggestion()783 public void testPinToBottomSuggestion() { 784 // each one reports 4 results; source 1 reports a pin-to-bottom suggestion last 785 mBacker.addSourceResults( 786 new SuggestionResult(mSource1, Lists.newArrayList( 787 makeSourceResult(mName1, 0), 788 makeSourceResult(mName1, 1), 789 makeSourceResult(mName1, 2), 790 makeSourceResult(mName1, 3), 791 makePinToBottomSourceResult(mName1, 4) 792 ))); 793 mBacker.addSourceResults( 794 new SuggestionResult(mSource2, Lists.newArrayList( 795 makeSourceResult(mName2, 0), 796 makeSourceResult(mName2, 1), 797 makeSourceResult(mName2, 2), 798 makeSourceResult(mName2, 3) 799 ))); 800 mBacker.addSourceResults( 801 new SuggestionResult(mSource3, Lists.newArrayList( 802 makeSourceResult(mName3, 0), 803 makeSourceResult(mName3, 1), 804 makeSourceResult(mName3, 2), 805 makeSourceResult(mName3, 3) 806 ))); 807 808 assertContentsInOrder( 809 "pin to bottom non-expanded.", 810 getSnapshotFromBacker(false), 811 mShortcut1, 812 makeSourceResult(mName1, 0), 813 makeSourceResult(mName1, 1), 814 makeSourceResult(mName2, 0), 815 makeSourceResult(mName2, 1), 816 makeSourceResult(mName1, 2), // remaining slots (source 3 is not promoted) 817 mSearchTheWeb, 818 makePinToBottomSourceResult(mName1, 4), 819 mMoreNotExpanded); 820 821 assertContentsInOrder( 822 "pin to bottom expanded.", 823 getSnapshotFromBacker(true), 824 mShortcut1, 825 makeSourceResult(mName1, 0), 826 makeSourceResult(mName1, 1), 827 makeSourceResult(mName2, 0), 828 makeSourceResult(mName2, 1), 829 makeSourceResult(mName1, 2), 830 mSearchTheWeb, 831 makePinToBottomSourceResult(mName1, 4), 832 mMoreExpanded, 833 makeCorpusEntry(SOURCE1_LABEL, SourceStat.RESPONSE_FINISHED, 1), // 1 remaining 834 makeCorpusEntry(SOURCE2_LABEL, SourceStat.RESPONSE_FINISHED, 2), // 2 remaining 835 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 4)); 836 } 837 testPinToBottomNotShownIfSourceNotInPromotedList()838 public void testPinToBottomNotShownIfSourceNotInPromotedList() { 839 // a couple source return before deadline 840 mBacker.addSourceResults( 841 new SuggestionResult(mSource2, Lists.newArrayList( 842 makeSourceResult(mName2, 0), 843 makeSourceResult(mName2, 1), 844 makeSourceResult(mName2, 2), 845 makeSourceResult(mName2, 3) 846 ))); 847 mBacker.addSourceResults( 848 new SuggestionResult(mSource3, Lists.newArrayList( 849 makeSourceResult(mName3, 0), 850 makeSourceResult(mName3, 1), 851 makeSourceResult(mName3, 2), 852 makeSourceResult(mName3, 3) 853 ))); 854 855 // another returns after the deadline with a pin to bottom 856 mBacker.setNow(NOW + DEADLINE); 857 mBacker.addSourceResults( 858 new SuggestionResult(mSource1, Lists.newArrayList( 859 makeSourceResult(mName1, 0), 860 makeSourceResult(mName1, 1), 861 makeSourceResult(mName1, 2), 862 makeSourceResult(mName1, 3), 863 makePinToBottomSourceResult(mName1, 4) 864 ))); 865 866 assertContentsInOrder( 867 "pin to bottom shouldn't be shown if its source responded after deadline.", 868 getSnapshotFromBacker(false), 869 mShortcut1, 870 makeSourceResult(mName2, 0), 871 makeSourceResult(mName2, 1), 872 makeSourceResult(mName2, 2), 873 makeSourceResult(mName2, 3), 874 mSearchTheWeb, 875 mMoreNotExpanded); 876 } 877 878 /** 879 * If non promoted sources report zero results before the user has expanded "more", then 880 * we don't bother showing "source X reported 0 results" upon expansion. 881 * 882 * We also remove the "more results" once we know there are no sources under "more results" 883 * to show. 884 */ testNonPromotedSourcesWithZeroResults_reportedBeforeViewed()885 public void testNonPromotedSourcesWithZeroResults_reportedBeforeViewed() { 886 mBacker.addSourceResults( 887 new SuggestionResult(mSource1, Lists.newArrayList( 888 makeSourceResult(mName1, 0), 889 makeSourceResult(mName1, 1) 890 ))); 891 mBacker.addSourceResults( 892 new SuggestionResult(mSource2, Lists.newArrayList( 893 makeSourceResult(mName2, 0), 894 makeSourceResult(mName2, 1) 895 ))); 896 897 assertContentsInOrder( 898 "non promoted source with zero results before viewed.", 899 getSnapshotFromBacker(false), 900 mShortcut1, 901 makeSourceResult(mName1, 0), 902 makeSourceResult(mName1, 1), 903 makeSourceResult(mName2, 0), 904 makeSourceResult(mName2, 1), 905 mSearchTheWeb, 906 mMoreNotExpanded); 907 908 // non-promoted source 3 909 mBacker.addSourceResults(new SuggestionResult(mSource3)); 910 911 assertContentsInOrder( 912 "non promoted source with zero results after expansion.", 913 getSnapshotFromBacker(true), // EXPANDED 914 mShortcut1, 915 makeSourceResult(mName1, 0), 916 makeSourceResult(mName1, 1), 917 makeSourceResult(mName2, 0), 918 makeSourceResult(mName2, 1), 919 mSearchTheWeb); // no "more" results entry at all 920 } 921 922 /** 923 * Similar to {@link #testNonPromotedSourcesWithZeroResults_reportedBeforeViewed()}, but in 924 * this case, the user has expanded the "more results" and thus viewed the entries; we can't 925 * remove them at this point and will continue to show them, even though they returned 926 * zero results. 927 */ testNonPromotedSourcesWithZeroResults_reportedAfterViewed()928 public void testNonPromotedSourcesWithZeroResults_reportedAfterViewed() { 929 mBacker.addSourceResults( 930 new SuggestionResult(mSource1, Lists.newArrayList( 931 makeSourceResult(mName1, 0), 932 makeSourceResult(mName1, 1) 933 ))); 934 mBacker.addSourceResults( 935 new SuggestionResult(mSource2, Lists.newArrayList( 936 makeSourceResult(mName2, 0), 937 makeSourceResult(mName2, 1) 938 ))); 939 940 assertContentsInOrder( 941 "non promoted source with zero results viewed before report.", 942 getSnapshotFromBacker(true), 943 mShortcut1, 944 makeSourceResult(mName1, 0), 945 makeSourceResult(mName1, 1), 946 makeSourceResult(mName2, 0), 947 makeSourceResult(mName2, 1), 948 mSearchTheWeb, 949 mMoreExpanded, 950 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_NOT_STARTED, 0)); 951 952 // non-promoted source 3 953 mBacker.addSourceResults(new SuggestionResult(mSource3)); 954 955 assertContentsInOrder( 956 "non promoted source with zero results pinned after being viewed.", 957 getSnapshotFromBacker(true), // EXPANDED 958 mShortcut1, 959 makeSourceResult(mName1, 0), 960 makeSourceResult(mName1, 1), 961 makeSourceResult(mName2, 0), 962 makeSourceResult(mName2, 1), 963 mSearchTheWeb, 964 mMoreExpanded, 965 makeCorpusEntry(SOURCE3_LABEL, SourceStat.RESPONSE_FINISHED, 0)); 966 } 967 getSnapshotFromBacker(boolean expandAdditional)968 List<SuggestionData> getSnapshotFromBacker(boolean expandAdditional) { 969 final ArrayList<SuggestionData> list = Lists.newArrayList(); 970 mBacker.snapshotSuggestions(list, expandAdditional); 971 return list; 972 } 973 makeCorpusEntry( String label, int responseStatus, int numResultsUndisplayed)974 private SuggestionData makeCorpusEntry( 975 String label, int responseStatus, int numResultsUndisplayed) { 976 final SuggestionData.Builder builder = new SuggestionData.Builder(mName1); 977 builder.title("more_" + label + " " + getResponseStatusString(responseStatus) 978 + ", numleft: " + numResultsUndisplayed); 979 return builder 980 .build(); 981 } 982 getResponseStatusString(int responseStatus)983 private String getResponseStatusString(int responseStatus) { 984 switch (responseStatus) { 985 case SourceStat.RESPONSE_NOT_STARTED: 986 return "RESPONSE_NOT_STARTED"; 987 case SourceStat.RESPONSE_IN_PROGRESS: 988 return "RESPONSE_IN_PROGRESS"; 989 case SourceStat.RESPONSE_FINISHED: 990 return "RESPONSE_FINISHED"; 991 default: 992 throw new IllegalArgumentException("unknown status " + responseStatus); 993 } 994 } 995 makeSourceResult(ComponentName name, int index)996 private SuggestionData makeSourceResult(ComponentName name, int index) { 997 return new SuggestionData.Builder(name) 998 .title(name.getClassName() + " " + index) 999 .intentAction(name.getClassName()) 1000 .intentData("" + index) 1001 .build(); 1002 } 1003 makePinToBottomSourceResult(ComponentName name, int index)1004 private SuggestionData makePinToBottomSourceResult(ComponentName name, int index) { 1005 return new SuggestionData.Builder(name) 1006 .title(name.getClassName() + " manage search history " + index) 1007 .intentAction(name.getClassName()) 1008 .intentData("" + index) 1009 .pinToBottom(true) 1010 .build(); 1011 } 1012 1013 /** 1014 * Allows setting what "now" is for testing 1015 */ 1016 private static class TestBacker extends SourceSuggestionBacker { 1017 1018 long now = 0L; 1019 TestBacker( String query, List<SuggestionData> shortcuts, List<SuggestionSource> sources, HashSet<ComponentName> promotedSources, SuggestionSource selectedWebSearchSource, List<SuggestionResult> cachedResults, SuggestionData goToWebsite, SuggestionData searchTheWeb, int maxPromotedSlots, long deadline, MoreExpanderFactory moreFactory, CorpusResultFactory corpusFactory)1020 public TestBacker( 1021 String query, 1022 List<SuggestionData> shortcuts, 1023 List<SuggestionSource> sources, 1024 HashSet<ComponentName> promotedSources, 1025 SuggestionSource selectedWebSearchSource, 1026 List<SuggestionResult> cachedResults, 1027 SuggestionData goToWebsite, 1028 SuggestionData searchTheWeb, 1029 int maxPromotedSlots, 1030 long deadline, 1031 MoreExpanderFactory moreFactory, 1032 CorpusResultFactory corpusFactory) { 1033 super(query, shortcuts, sources, promotedSources, selectedWebSearchSource, cachedResults, 1034 goToWebsite, searchTheWeb, maxPromotedSlots, deadline, moreFactory, 1035 corpusFactory); 1036 } 1037 1038 getNow()1039 public long getNow() { 1040 return now; 1041 } 1042 setNow(long now)1043 public void setNow(long now) { 1044 this.now = now; 1045 } 1046 } 1047 assertContentsInOrder(Iterable<?> actual, Object... expected)1048 static void assertContentsInOrder(Iterable<?> actual, Object... expected) { 1049 assertContentsInOrder(null, actual, expected); 1050 } 1051 1052 /** 1053 * an implementation of {@link MoreAsserts#assertContentsInOrder(String, Iterable, Object[])} 1054 * with some additional information placed in the assert message in the error case to make it 1055 * easier to see where the mismatch is. 1056 */ assertContentsInOrder( String message, Iterable<?> actual, Object... expected)1057 static void assertContentsInOrder( 1058 String message, Iterable<?> actual, Object... expected) { 1059 ArrayList actualList = new ArrayList(); 1060 for (Object o : actual) { 1061 actualList.add(o); 1062 } 1063 StringBuilder sb = new StringBuilder(); 1064 if (message != null) sb.append(message); 1065 final List<Object> expectedList = Arrays.asList(expected); 1066 1067 if (expectedList.size() != actualList.size()) { 1068 sb.append("\nsize mismatch (expected: ").append(expectedList.size()) 1069 .append(" actual: ").append(actualList.size()).append('.'); 1070 } 1071 for (int i = 0; i < Math.min(expectedList.size(), actualList.size()); i++) { 1072 final Object expectedItem = expectedList.get(i); 1073 final Object actualItem = actualList.get(i); 1074 if (!expectedItem.equals(actualItem)) { 1075 sb.append("\n").append("at index ").append(i) 1076 .append(" expected: ").append(expectedItem) 1077 .append("\n").append("actual: ").append(actualItem); 1078 } 1079 } 1080 Assert.assertEquals(sb.toString(), expectedList, actualList); 1081 } 1082 } 1083