From 01b023730ee3d86d60016c21915608376c724442 Mon Sep 17 00:00:00 2001
From: Jatin Matani <jatinm@google.com>
Date: Tue, 10 Feb 2015 11:39:34 -0800
Subject: [PATCH] Add tests for Contact* classes

Also add empty implementation for dictionary
facilitator iteration APIs
Change-Id: Ia847e3faa75075c819fcfda89193d1024d2d5aca
---
 .../latin/ContactsContentObserver.java        |   3 +-
 .../inputmethod/latin/ContactsManager.java    |   1 -
 .../latin/DictionaryFacilitator.java          |  13 ++
 .../latin/DictionaryFacilitatorImpl.java      |   8 ++
 .../latin/ContactsContentObserverTest.java    |  94 +++++++++++++
 .../latin/ContactsDictionaryUtilsTest.java    |  61 +++++++++
 .../latin/ContactsManagerTest.java            | 127 ++++++++++++++++++
 7 files changed, 304 insertions(+), 3 deletions(-)
 create mode 100644 tests/src/com/android/inputmethod/latin/ContactsContentObserverTest.java
 create mode 100644 tests/src/com/android/inputmethod/latin/ContactsDictionaryUtilsTest.java
 create mode 100644 tests/src/com/android/inputmethod/latin/ContactsManagerTest.java

diff --git a/java/src/com/android/inputmethod/latin/ContactsContentObserver.java b/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
index 777bfe6f67..e45681bd7c 100644
--- a/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
+++ b/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
@@ -32,7 +32,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 /**
  * A content observer that listens to updates to content provider {@link Contacts#CONTENT_URI}.
  */
-// TODO:add test
 public class ContactsContentObserver implements Runnable {
     private static final String TAG = ContactsContentObserver.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -82,7 +81,7 @@ public class ContactsContentObserver implements Runnable {
         sRunning.set(false);
     }
 
-    private boolean haveContentsChanged() {
+    boolean haveContentsChanged() {
         final long startTime = SystemClock.uptimeMillis();
         final int contactCount = mManager.getContactCount();
         if (contactCount > ContactsDictionaryConstants.MAX_CONTACT_COUNT) {
diff --git a/java/src/com/android/inputmethod/latin/ContactsManager.java b/java/src/com/android/inputmethod/latin/ContactsManager.java
index dc5abd955f..1fadc6f6f5 100644
--- a/java/src/com/android/inputmethod/latin/ContactsManager.java
+++ b/java/src/com/android/inputmethod/latin/ContactsManager.java
@@ -34,7 +34,6 @@ import java.util.concurrent.atomic.AtomicInteger;
  * The manager provides an API for listening to meaning full updates by keeping a
  * measure of the current state of the content provider.
  */
-// TODO:Add test
 public class ContactsManager {
     private static final String TAG = ContactsManager.class.getSimpleName();
     private static final boolean DEBUG = false;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 9f48501d60..22f5f5caf0 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -181,4 +181,17 @@ public interface DictionaryFacilitator {
             int timeStampInSeconds);
 
     void clearLanguageModel(String filePath);
+
+    /**
+     * Lets callers iterate over a given dynamic language model. Each iterate call
+     * results in ngrams, their counts, their last updated timestamps and an iteration token
+     * that can be used for the next {@link #iterateOverLanguageModel} call.
+     *
+     * Use empty string for starting the iterator from the begining.
+     * Returns empty string if there are no more entries to iterate upon.
+     * TODO: Encapsulate the result arrays into a java class.
+     */
+    String  iterateOverLanguageModel(String filePath, String iterationToken,
+            ArrayList<String> outputNgramEntries, ArrayList<Integer> outputNgramCounts,
+            ArrayList<Integer> outputNgramTimestamps);
 }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
index f67e3a9387..6bc97caab6 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
@@ -815,4 +815,12 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
     public void clearLanguageModel(String filePath) {
         // Do nothing.
     }
+
+    @Override
+    public String iterateOverLanguageModel(String filePath, String iterationToken,
+            ArrayList<String> outputNgramEntries, ArrayList<Integer> outputNgramCounts,
+            ArrayList<Integer> outputNgramTimestamps) {
+        // Do nothing.
+        return "";
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/ContactsContentObserverTest.java b/tests/src/com/android/inputmethod/latin/ContactsContentObserverTest.java
new file mode 100644
index 0000000000..f90a18bf17
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/ContactsContentObserverTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.validateMockitoUsage;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.ContactsContract.Contacts;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for {@link ContactsContentObserver}.
+ */
+@SmallTest
+public class ContactsContentObserverTest {
+    private static final int UPDATED_CONTACT_COUNT = 10;
+    private static final int STALE_CONTACT_COUNT = 8;
+    private static final ArrayList<String> STALE_NAMES_LIST = new ArrayList<>();
+    private static final ArrayList<String> UPDATED_NAMES_LIST = new ArrayList<>();
+
+    static {
+        STALE_NAMES_LIST.add("Larry Page");
+        STALE_NAMES_LIST.add("Roger Federer");
+        UPDATED_NAMES_LIST.add("Larry Page");
+        UPDATED_NAMES_LIST.add("Roger Federer");
+        UPDATED_NAMES_LIST.add("Barak Obama");
+    }
+
+    @Mock private ContactsManager mMockManager;
+    @Mock private Context mContext;
+
+    private ContactsContentObserver mObserver;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mObserver = new ContactsContentObserver(mMockManager, mContext);
+    }
+
+    @After
+    public void tearDown() {
+        validateMockitoUsage();
+    }
+
+    @Test
+    public void testHaveContentsChanged_NoChange() {
+        when(mMockManager.getContactCount()).thenReturn(STALE_CONTACT_COUNT);
+        when(mMockManager.getContactCountAtLastRebuild()).thenReturn(STALE_CONTACT_COUNT);
+        when(mMockManager.getValidNames(eq(Contacts.CONTENT_URI))).thenReturn(STALE_NAMES_LIST);
+        when(mMockManager.getHashCodeAtLastRebuild()).thenReturn(STALE_NAMES_LIST.hashCode());
+        assertFalse(mObserver.haveContentsChanged());
+    }
+    @Test
+    public void testHaveContentsChanged_UpdatedCount() {
+        when(mMockManager.getContactCount()).thenReturn(UPDATED_CONTACT_COUNT);
+        when(mMockManager.getContactCountAtLastRebuild()).thenReturn(STALE_CONTACT_COUNT);
+        assertTrue(mObserver.haveContentsChanged());
+    }
+
+    @Test
+    public void testHaveContentsChanged_HashUpdate() {
+        when(mMockManager.getContactCount()).thenReturn(STALE_CONTACT_COUNT);
+        when(mMockManager.getContactCountAtLastRebuild()).thenReturn(STALE_CONTACT_COUNT);
+        when(mMockManager.getValidNames(eq(Contacts.CONTENT_URI))).thenReturn(UPDATED_NAMES_LIST);
+        when(mMockManager.getHashCodeAtLastRebuild()).thenReturn(STALE_NAMES_LIST.hashCode());
+        assertTrue(mObserver.haveContentsChanged());
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/ContactsDictionaryUtilsTest.java b/tests/src/com/android/inputmethod/latin/ContactsDictionaryUtilsTest.java
new file mode 100644
index 0000000000..9b49f1abb0
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/ContactsDictionaryUtilsTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.util.Locale;
+
+/**
+ * Tests for {@link ContactsDictionaryUtils}
+ */
+@SmallTest
+public class ContactsDictionaryUtilsTest {
+
+    @Test
+    public void testGetWordEndPosition() {
+        final String testString1 = "Larry Page";
+        assertEquals(5, ContactsDictionaryUtils.getWordEndPosition(
+                testString1, testString1.length(), 0 /* startIndex */));
+
+        assertEquals(10, ContactsDictionaryUtils.getWordEndPosition(
+                testString1, testString1.length(), 6 /* startIndex */));
+
+        final String testString2 = "Larry-Page";
+        assertEquals(10, ContactsDictionaryUtils.getWordEndPosition(
+                testString2, testString1.length(), 0 /* startIndex */));
+
+        final String testString3 = "Larry'Page";
+        assertEquals(10, ContactsDictionaryUtils.getWordEndPosition(
+                testString3, testString1.length(), 0 /* startIndex */));
+    }
+
+    @Test
+    public void testUseFirstLastBigramsForLocale() {
+        assertTrue(ContactsDictionaryUtils.useFirstLastBigramsForLocale(Locale.ENGLISH));
+        assertTrue(ContactsDictionaryUtils.useFirstLastBigramsForLocale(Locale.US));
+        assertTrue(ContactsDictionaryUtils.useFirstLastBigramsForLocale(Locale.UK));
+        assertFalse(ContactsDictionaryUtils.useFirstLastBigramsForLocale(Locale.CHINA));
+        assertFalse(ContactsDictionaryUtils.useFirstLastBigramsForLocale(Locale.GERMAN));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/ContactsManagerTest.java b/tests/src/com/android/inputmethod/latin/ContactsManagerTest.java
new file mode 100644
index 0000000000..6326b3b0f7
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/ContactsManagerTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.test.AndroidTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Tests for {@link ContactsManager}
+ */
+@SmallTest
+public class ContactsManagerTest extends AndroidTestCase {
+
+    private ContactsManager mManager;
+    private FakeContactsContentProvider mFakeContactsContentProvider;
+    private MatrixCursor mMatrixCursor;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        // Fake content provider
+        mFakeContactsContentProvider = new FakeContactsContentProvider();
+        mMatrixCursor = new MatrixCursor(ContactsDictionaryConstants.PROJECTION);
+        // Add the fake content provider to fake content resolver.
+        final MockContentResolver contentResolver = new MockContentResolver();
+        contentResolver.addProvider(ContactsContract.AUTHORITY, mFakeContactsContentProvider);
+        // Add the fake content resolver to a fake context.
+        final ContextWithMockContentResolver context = new ContextWithMockContentResolver(mContext);
+        context.setContentResolver(contentResolver);
+
+        mManager = new ContactsManager(context);
+    }
+
+    @Test
+    public void testGetValidNames() {
+        final String contactName1 = "firstname lastname";
+        final String contactName2 = "larry";
+        mMatrixCursor.addRow(new Object[] { 1, contactName1 });
+        mMatrixCursor.addRow(new Object[] { 2, null /* null name */ });
+        mMatrixCursor.addRow(new Object[] { 3, contactName2 });
+        mMatrixCursor.addRow(new Object[] { 4, "floopy@example.com" /* invalid name */ });
+        mFakeContactsContentProvider.addQueryResult(Contacts.CONTENT_URI, mMatrixCursor);
+
+        final ArrayList<String> validNames = mManager.getValidNames(Contacts.CONTENT_URI);
+        assertEquals(2, validNames.size());
+        assertEquals(contactName1, validNames.get(0));
+        assertEquals(contactName2, validNames.get(1));
+    }
+
+    @Test
+    public void testGetCount() {
+        mMatrixCursor.addRow(new Object[] { 1, "firstname" });
+        mMatrixCursor.addRow(new Object[] { 2, null /* null name */ });
+        mMatrixCursor.addRow(new Object[] { 3, "larry" });
+        mMatrixCursor.addRow(new Object[] { 4, "floopy@example.com" /* invalid name */ });
+        mFakeContactsContentProvider.addQueryResult(Contacts.CONTENT_URI, mMatrixCursor);
+
+        assertEquals(4, mManager.getContactCount());
+    }
+
+
+    static class ContextWithMockContentResolver extends RenamingDelegatingContext {
+        private ContentResolver contentResolver;
+
+        public void setContentResolver(final ContentResolver contentResolver) {
+            this.contentResolver = contentResolver;
+        }
+
+        public ContextWithMockContentResolver(final Context targetContext) {
+            super(targetContext, "test");
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return contentResolver;
+        }
+    }
+
+    static class FakeContactsContentProvider extends MockContentProvider {
+        private final HashMap<String, MatrixCursor> mQueryCursorMapForTestExpectations =
+                new HashMap<>();
+
+        @Override
+        public Cursor query(final Uri uri, final String[] projection, final String selection,
+                final String[] selectionArgs, final String sortOrder) {
+            return mQueryCursorMapForTestExpectations.get(uri.toString());
+        }
+
+        public void reset() {
+            mQueryCursorMapForTestExpectations.clear();
+        }
+
+        public void addQueryResult(final Uri uri, final MatrixCursor cursor) {
+            mQueryCursorMapForTestExpectations.put(uri.toString(), cursor);
+        }
+    }
+}
-- 
GitLab