1 /* 2 * Copyright (C) 2010 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.quicksearchbox; 18 19 import android.test.AndroidTestCase; 20 import android.text.Spanned; 21 22 import androidx.test.filters.SmallTest; 23 24 import com.android.quicksearchbox.MockTextAppearanceFactory.MockStyleSpan; 25 import com.android.quicksearchbox.util.LevenshteinDistance.Token; 26 27 /** 28 * Tests for {@link LevenshteinSuggestionFormatter}. 29 */ 30 @SmallTest 31 public class LevenshteinFormatterTest extends AndroidTestCase { 32 33 private LevenshteinSuggestionFormatter mFormatter; 34 private int mSuggestedStyle; 35 private int mQueryStyle; 36 37 @Override setUp()38 protected void setUp() throws Exception { 39 mFormatter = new LevenshteinSuggestionFormatter(new MockTextAppearanceFactory()); 40 mSuggestedStyle = MockTextAppearanceFactory.ID_SUGGESTED_TEXT; 41 mQueryStyle = MockTextAppearanceFactory.ID_QUERY_TEXT; 42 } 43 verifyTokenizeResult(String input, String... output)44 private void verifyTokenizeResult(String input, String... output) { 45 Token[] tokens = mFormatter.tokenize(input); 46 assertEquals(output.length, tokens.length); 47 for (int i=0; i<output.length; ++i) { 48 assertEquals(output[i], tokens[i].toString()); 49 } 50 } 51 testTokenizeNoTokens()52 public void testTokenizeNoTokens() { 53 verifyTokenizeResult(""); 54 verifyTokenizeResult(" "); 55 } 56 testTokenizeSingleToken()57 public void testTokenizeSingleToken() { 58 verifyTokenizeResult("singleToken", "singleToken"); 59 } 60 testTokenizeTwoTokens()61 public void testTokenizeTwoTokens() { 62 verifyTokenizeResult("two tokens", "two", "tokens"); 63 } 64 testTokenizeLeadingSpaces()65 public void testTokenizeLeadingSpaces() { 66 verifyTokenizeResult(" evil kittens", "evil", "kittens"); 67 verifyTokenizeResult(" furry lizards", "furry", "lizards"); 68 } 69 testTokenizeTrailingSpaces()70 public void testTokenizeTrailingSpaces() { 71 verifyTokenizeResult("mechanical elephant ", "mechanical", "elephant"); 72 verifyTokenizeResult("disappointed dog ", "disappointed", "dog"); 73 } 74 testTokenizeManySpaces()75 public void testTokenizeManySpaces() { 76 verifyTokenizeResult("happy horses", "happy", "horses"); 77 } 78 testTokenizeLongSentence()79 public void testTokenizeLongSentence() { 80 verifyTokenizeResult("The fool looks at a finger that points at the sky", 81 "The", "fool", "looks", "at", "a", "finger", "that", "points", "at", "the", "sky"); 82 } 83 testTokenizeWithPunctuation()84 public void testTokenizeWithPunctuation() { 85 verifyTokenizeResult("Hitchhiker's guide", "Hitchhiker's", "guide"); 86 verifyTokenizeResult("full. stop. ", "full.", "stop."); 87 verifyTokenizeResult("' . ; . ..", "'", ".", ";", ".", ".."); 88 } 89 testTokenizeWithTabs()90 public void testTokenizeWithTabs() { 91 verifyTokenizeResult("don't\tpanic\t", "don't", "panic"); 92 } 93 verifyFindMatches(String source, String target, String... newTokensInTarget)94 private void verifyFindMatches(String source, String target, String... newTokensInTarget) { 95 Token[] sourceTokens = mFormatter.tokenize(source); 96 Token[] targetTokens = mFormatter.tokenize(target); 97 98 int[] matches = mFormatter.findMatches(sourceTokens, targetTokens); 99 assertEquals(targetTokens.length, matches.length); 100 int newTokenCount = 0; 101 int lastSourceToken = -1; 102 for (int i=0; i<targetTokens.length; ++i) { 103 104 int sourceIdx = matches[i]; 105 if (sourceIdx < 0) { 106 String targetToken = targetTokens[i].toString(); 107 assertTrue("Unexpected new token '" + targetToken + "'", 108 newTokenCount < newTokensInTarget.length); 109 110 assertEquals(newTokensInTarget[newTokenCount], targetToken); 111 ++newTokenCount; 112 } else { 113 assertTrue("Source token out of order", lastSourceToken < sourceIdx); 114 Token srcToken = sourceTokens[sourceIdx]; 115 Token trgToken = targetTokens[i]; 116 assertTrue("'" + srcToken + "' is not a prefix of '" + trgToken + "'", 117 srcToken.prefixOf(trgToken)); 118 lastSourceToken = sourceIdx; 119 } 120 } 121 } 122 testFindMatchesSameTokens()123 public void testFindMatchesSameTokens() { 124 verifyFindMatches("", ""); 125 verifyFindMatches("one", "one"); 126 verifyFindMatches("one two three", "one two three"); 127 } 128 testFindMatchesNewTokens()129 public void testFindMatchesNewTokens() { 130 verifyFindMatches("", "one", "one"); 131 verifyFindMatches("one", "one two", "two"); 132 verifyFindMatches("one", "one two three", "two", "three"); 133 verifyFindMatches("two", "one two three", "one", "three"); 134 verifyFindMatches("pictures", "pictures of kittens", "of", "kittens"); 135 } 136 testFindMatchesReplacedTokens()137 public void testFindMatchesReplacedTokens() { 138 verifyFindMatches("one", "two", "two"); 139 verifyFindMatches("one", "two three", "two", "three"); 140 verifyFindMatches("two", "one three", "one", "three"); 141 verifyFindMatches("pictures", "of kittens", "of", "kittens"); 142 } 143 testFindMatchesDuplicateTokens()144 public void testFindMatchesDuplicateTokens() { 145 verifyFindMatches("badger", "badger badger", "badger"); 146 verifyFindMatches("badger", "badger badger badger", "badger", "badger"); 147 verifyFindMatches("badger badger", "badger badger badger", "badger"); 148 verifyFindMatches("badger badger badger", "badger badger badger"); 149 // mushroom! 150 } 151 verifyFormatSuggestion(String query, String suggestion, SpanFormat... spans)152 private void verifyFormatSuggestion(String query, String suggestion, SpanFormat... spans) { 153 Spanned s = mFormatter.formatSuggestion(query, suggestion); 154 for (SpanFormat span : spans) { 155 span.verify(s); 156 } 157 } 158 testFormatSuggestionEmptyStrings()159 public void testFormatSuggestionEmptyStrings() { 160 verifyFormatSuggestion("", ""); 161 } 162 testFormatSuggestionEmptyQuery()163 public void testFormatSuggestionEmptyQuery() { 164 verifyFormatSuggestion("", "suggestion", 165 new SpanFormat(0, "suggestion", mSuggestedStyle)); 166 } 167 testFormatSuggestionQuerySuggested()168 public void testFormatSuggestionQuerySuggested() { 169 verifyFormatSuggestion("query", "query", 170 new SpanFormat(0, "query", mQueryStyle)); 171 } 172 testFormatSuggestionExtraWordsSuggested()173 public void testFormatSuggestionExtraWordsSuggested() { 174 verifyFormatSuggestion("query", "query suggested", 175 new SpanFormat(0, "query", mQueryStyle), 176 new SpanFormat(6, "suggested", mSuggestedStyle)); 177 178 verifyFormatSuggestion("pictures", "pictures of kittens", 179 new SpanFormat(0, "pictures", mQueryStyle), 180 new SpanFormat(9, "of", mSuggestedStyle), 181 new SpanFormat(12, "kittens", mSuggestedStyle)); 182 183 verifyFormatSuggestion("pictures of", "pictures of kittens dying", 184 new SpanFormat(0, "pictures", mQueryStyle), 185 new SpanFormat(9, "of", mQueryStyle), 186 new SpanFormat(12, "kittens", mSuggestedStyle), 187 new SpanFormat(20, "dying", mSuggestedStyle)); 188 } 189 testFormatSuggestionExtraWordSuggestedAtStart()190 public void testFormatSuggestionExtraWordSuggestedAtStart() { 191 verifyFormatSuggestion("query", "suggested query", 192 new SpanFormat(0, "suggested", mSuggestedStyle), 193 new SpanFormat(10, "query", mQueryStyle)); 194 } 195 testFormatSuggestionAlternativeWordSuggested()196 public void testFormatSuggestionAlternativeWordSuggested() { 197 verifyFormatSuggestion("query", "suggested", 198 new SpanFormat(0, "suggested", mSuggestedStyle)); 199 } 200 testFormatSuggestionDuplicateWords()201 public void testFormatSuggestionDuplicateWords() { 202 verifyFormatSuggestion("", "badger badger", 203 new SpanFormat(0, "badger", mSuggestedStyle), 204 new SpanFormat(7, "badger", mSuggestedStyle)); 205 206 verifyFormatSuggestion("badger", "badger badger", 207 new SpanFormat(0, "badger", mQueryStyle), 208 new SpanFormat(7, "badger", mSuggestedStyle)); 209 210 verifyFormatSuggestion("badger badger", "badger badger", 211 new SpanFormat(0, "badger", mQueryStyle), 212 new SpanFormat(7, "badger", mQueryStyle)); 213 214 verifyFormatSuggestion("badger badger", "badger badger badger", 215 new SpanFormat(0, "badger", mQueryStyle), 216 new SpanFormat(7, "badger", mQueryStyle), 217 new SpanFormat(14, "badger", mSuggestedStyle)); 218 } 219 testFormatSuggestionDuplicateSequences()220 public void testFormatSuggestionDuplicateSequences() { 221 verifyFormatSuggestion("dem bones", "dem bones dem bones", 222 new SpanFormat(0, "dem", mQueryStyle), 223 new SpanFormat(4, "bones", mQueryStyle), 224 new SpanFormat(10, "dem", mSuggestedStyle), 225 new SpanFormat(14, "bones", mSuggestedStyle) 226 ); 227 228 verifyFormatSuggestion("dem bones", "dem dry bones dem bones", 229 new SpanFormat(0, "dem", mQueryStyle), 230 new SpanFormat(4, "dry", mSuggestedStyle), 231 new SpanFormat(8, "bones", mQueryStyle), 232 new SpanFormat(14, "dem", mSuggestedStyle), 233 new SpanFormat(18, "bones", mSuggestedStyle) 234 ); 235 236 verifyFormatSuggestion("dem dry bones", "dry bones dem dry bones dem dry bones", 237 new SpanFormat(0, "dry", mSuggestedStyle), 238 new SpanFormat(4, "bones", mSuggestedStyle), 239 new SpanFormat(10, "dem", mQueryStyle), 240 new SpanFormat(14, "dry", mQueryStyle), 241 new SpanFormat(18, "bones", mQueryStyle), 242 new SpanFormat(24, "dem", mSuggestedStyle), 243 new SpanFormat(28, "dry", mSuggestedStyle), 244 new SpanFormat(32, "bones", mSuggestedStyle) 245 ); 246 } 247 testFormatSuggestionWordCompletion()248 public void testFormatSuggestionWordCompletion() { 249 verifyFormatSuggestion("hitch", "hitchhiker", 250 new SpanFormat(0, "hitch", mQueryStyle), 251 new SpanFormat(5, "hiker", mSuggestedStyle) 252 ); 253 254 verifyFormatSuggestion("hitch", "hitchhiker's guide", 255 new SpanFormat(0, "hitch", mQueryStyle), 256 new SpanFormat(5, "hiker's", mSuggestedStyle), 257 new SpanFormat(13, "guide", mSuggestedStyle) 258 ); 259 260 verifyFormatSuggestion("hitchhiker's g", "hitchhiker's guide", 261 new SpanFormat(0, "hitchhiker's", mQueryStyle), 262 new SpanFormat(13, "g", mQueryStyle), 263 new SpanFormat(14, "uide", mSuggestedStyle) 264 ); 265 266 verifyFormatSuggestion("hitchhiker's g", "hitchhiker's guide to the galaxy", 267 new SpanFormat(0, "hitchhiker's", mQueryStyle), 268 new SpanFormat(13, "g", mQueryStyle), 269 new SpanFormat(14, "uide", mSuggestedStyle), 270 new SpanFormat(19, "to", mSuggestedStyle), 271 new SpanFormat(22, "the", mSuggestedStyle), 272 new SpanFormat(26, "galaxy", mSuggestedStyle) 273 ); 274 } 275 testFormatSuggestionWordSplitting()276 public void testFormatSuggestionWordSplitting() { 277 verifyFormatSuggestion("dimsum", "dim sum", 278 new SpanFormat(0, "dim", mSuggestedStyle), 279 new SpanFormat(4, "sum", mSuggestedStyle) 280 ); 281 282 verifyFormatSuggestion("dimsum london", "dim sum london", 283 new SpanFormat(0, "dim", mSuggestedStyle), 284 new SpanFormat(4, "sum", mSuggestedStyle), 285 new SpanFormat(8, "london", mQueryStyle) 286 ); 287 288 verifyFormatSuggestion("dimsum london", "dim sum london yummy", 289 new SpanFormat(0, "dim", mSuggestedStyle), 290 new SpanFormat(4, "sum", mSuggestedStyle), 291 new SpanFormat(8, "london", mQueryStyle), 292 new SpanFormat(15, "yummy", mSuggestedStyle) 293 ); 294 } 295 testFormatSuggestionWordCombining()296 public void testFormatSuggestionWordCombining() { 297 verifyFormatSuggestion("hos pital", "hospital", 298 new SpanFormat(0, "hos", mQueryStyle), 299 new SpanFormat(3, "pital", mSuggestedStyle) 300 ); 301 302 verifyFormatSuggestion("hos pital", "hospital waiting times", 303 new SpanFormat(0, "hos", mQueryStyle), 304 new SpanFormat(3, "pital", mSuggestedStyle), 305 new SpanFormat(9, "waiting", mSuggestedStyle), 306 new SpanFormat(17, "times", mSuggestedStyle) 307 ); 308 309 verifyFormatSuggestion("hos pital waiting", "hospital waiting", 310 new SpanFormat(0, "hos", mQueryStyle), 311 new SpanFormat(3, "pital", mSuggestedStyle), 312 new SpanFormat(9, "waiting", mQueryStyle) 313 ); 314 315 verifyFormatSuggestion("hospital wait ing times", "hospital waiting times", 316 new SpanFormat(0, "hospital", mQueryStyle), 317 new SpanFormat(9, "wait", mQueryStyle), 318 new SpanFormat(13, "ing", mSuggestedStyle), 319 new SpanFormat(17, "times", mQueryStyle) 320 ); 321 } 322 testFormatSuggestionCapitalization()323 public void testFormatSuggestionCapitalization() { 324 verifyFormatSuggestion("Flay", "flay", 325 new SpanFormat(0, "flay", mQueryStyle)); 326 327 verifyFormatSuggestion("STEERPI", "steerpike", 328 new SpanFormat(0, "steerpi", mQueryStyle), 329 new SpanFormat(7, "ke", mSuggestedStyle)); 330 331 verifyFormatSuggestion("STEErpi", "steerpike", 332 new SpanFormat(0, "steerpi", mQueryStyle), 333 new SpanFormat(7, "ke", mSuggestedStyle)); 334 335 verifyFormatSuggestion("TITUS", "titus groan", 336 new SpanFormat(0, "titus", mQueryStyle), 337 new SpanFormat(6, "groan", mSuggestedStyle)); 338 } 339 340 private class SpanFormat { 341 private final int mStart; 342 private final int mEnd; 343 private final String mExpectedText; 344 private final int mStyle; SpanFormat(int start, String expectedText, int style)345 public SpanFormat(int start, String expectedText, int style) { 346 mStart = start; 347 mEnd = start + expectedText.length(); 348 mExpectedText = expectedText; 349 mStyle = style; 350 } verify(Spanned spanned)351 public void verify(Spanned spanned) { 352 String spannedText = spanned.subSequence(mStart, mEnd).toString(); 353 assertEquals("Test error", mExpectedText, spannedText); 354 MockStyleSpan[] spans = spanned.getSpans(mStart, mEnd, MockStyleSpan.class); 355 assertEquals("Wrong number of spans in '" + spannedText + "'", 1, spans.length); 356 assertEquals("Wrong style for '" + spannedText + "' at position " + mStart, 357 mStyle, spans[0].getId()); 358 } 359 } 360 361 } 362