From 4e9ff5c115e1b61ab33e43c0efa83a42c1b28efd Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Tue, 2 Nov 2021 21:46:58 +0530 Subject: [PATCH 1/9] deleted module and related files along with occurrences --- .buildconfig.yml | 4 - CODEOWNERS | 1 - README.md | 2 - components/browser/storage-memory/README.md | 17 - .../browser/storage-memory/build.gradle | 39 -- .../browser/storage-memory/proguard-rules.pro | 21 - .../src/main/AndroidManifest.xml | 5 - .../storage/memory/InMemoryHistoryStorage.kt | 200 --------- .../memory/InMemoryHistoryStorageTest.kt | 401 ------------------ .../src/test/resources/robolectric.properties | 1 - components/concept/storage/README.md | 1 - components/feature/awesomebar/build.gradle | 1 - .../CombinedHistorySuggestionProviderTest.kt | 202 ++++++++- .../HistoryStorageSuggestionProviderTest.kt | 202 ++++++++- components/feature/toolbar/build.gradle | 1 - .../toolbar/ToolbarAutocompleteFeatureTest.kt | 188 +++++++- taskcluster/ci/config.yml | 1 - 17 files changed, 554 insertions(+), 733 deletions(-) delete mode 100644 components/browser/storage-memory/README.md delete mode 100644 components/browser/storage-memory/build.gradle delete mode 100644 components/browser/storage-memory/proguard-rules.pro delete mode 100644 components/browser/storage-memory/src/main/AndroidManifest.xml delete mode 100644 components/browser/storage-memory/src/main/java/mozilla/components/browser/storage/memory/InMemoryHistoryStorage.kt delete mode 100644 components/browser/storage-memory/src/test/java/mozilla/components/browser/storage/memory/InMemoryHistoryStorageTest.kt delete mode 100644 components/browser/storage-memory/src/test/resources/robolectric.properties diff --git a/.buildconfig.yml b/.buildconfig.yml index 06553c95a67..0b0317c6ffb 100644 --- a/.buildconfig.yml +++ b/.buildconfig.yml @@ -235,10 +235,6 @@ projects: path: components/browser/state description: 'Component responsible for maintaining the centralized state of a browser engine.' publish: true - browser-storage-memory: - path: components/browser/storage-memory - description: 'A memory-backed implementation of core data storage.' - publish: true browser-storage-sync: path: components/browser/storage-sync description: 'A syncable, Rust Places-backed implementation of core data storage.' diff --git a/CODEOWNERS b/CODEOWNERS index baad841cc54..5f1781c3773 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -40,7 +40,6 @@ /components/browser/menu2/ @jonalmeida /components/browser/search/ @pocmo /components/browser/state/ @pocmo @csadilek -/components/browser/storage-memory/ @grigoryk /components/browser/storage-sync/ @grigoryk /components/browser/tabstray/ @jonalmeida /components/browser/thumbnails/ @jonalmeida @gabrielluong diff --git a/README.md b/README.md index 5fe48e7d937..ba686fa2492 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,6 @@ High-level components for building browser(-like) apps. * πŸ”΅ [**State**](components/browser/state/README.md) - Component for maintaining the centralized state of the browser and its components. -* πŸ”΅ [**Storage-Memory**](components/browser/storage-memory/README.md) - An in-memory implementation of browser storage. - * πŸ”΅ [**Storage-Sync**](components/browser/storage-sync/README.md) - A syncable implementation of browser storage backed by [application-services' Places lib](https://github.com/mozilla/application-services). * βšͺ [**Tabstray**](components/browser/tabstray/README.md) - A customizable tabs tray for browsers. diff --git a/components/browser/storage-memory/README.md b/components/browser/storage-memory/README.md deleted file mode 100644 index 683b6ce3808..00000000000 --- a/components/browser/storage-memory/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# [Android Components](../../../README.md) > Browser > Memory Storage - -An in-memory implementation of `concept-storage`. Data is not persisted to disk, and does not survive a restart. Not suitable for syncing. - -### Setting up the dependency - -Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)): - -```Groovy -implementation "org.mozilla.components:browser-storage-memory:{latest-version}" -``` - -## License - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/ diff --git a/components/browser/storage-memory/build.gradle b/components/browser/storage-memory/build.gradle deleted file mode 100644 index c6aec653ba4..00000000000 --- a/components/browser/storage-memory/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - compileSdkVersion config.compileSdkVersion - - defaultConfig { - minSdkVersion config.minSdkVersion - targetSdkVersion config.targetSdkVersion - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - // This dependency is part of this module's public API. - api project(':concept-storage') - implementation project(':support-utils') - - implementation Dependencies.kotlin_stdlib - - testImplementation Dependencies.androidx_test_junit - testImplementation Dependencies.testing_robolectric - testImplementation Dependencies.testing_mockito - - testImplementation project(':support-test') -} - -apply from: '../../../publish.gradle' -ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) diff --git a/components/browser/storage-memory/proguard-rules.pro b/components/browser/storage-memory/proguard-rules.pro deleted file mode 100644 index f1b424510da..00000000000 --- a/components/browser/storage-memory/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/components/browser/storage-memory/src/main/AndroidManifest.xml b/components/browser/storage-memory/src/main/AndroidManifest.xml deleted file mode 100644 index 92faf390ed7..00000000000 --- a/components/browser/storage-memory/src/main/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/components/browser/storage-memory/src/main/java/mozilla/components/browser/storage/memory/InMemoryHistoryStorage.kt b/components/browser/storage-memory/src/main/java/mozilla/components/browser/storage/memory/InMemoryHistoryStorage.kt deleted file mode 100644 index 858287c4231..00000000000 --- a/components/browser/storage-memory/src/main/java/mozilla/components/browser/storage/memory/InMemoryHistoryStorage.kt +++ /dev/null @@ -1,200 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package mozilla.components.browser.storage.memory - -import androidx.annotation.VisibleForTesting -import mozilla.components.concept.storage.FrecencyThresholdOption -import mozilla.components.concept.storage.HistoryAutocompleteResult -import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.concept.storage.PageObservation -import mozilla.components.concept.storage.PageVisit -import mozilla.components.concept.storage.RedirectSource -import mozilla.components.concept.storage.SearchResult -import mozilla.components.concept.storage.TopFrecentSiteInfo -import mozilla.components.concept.storage.VisitInfo -import mozilla.components.concept.storage.VisitType -import mozilla.components.support.utils.StorageUtils.levenshteinDistance -import mozilla.components.support.utils.segmentAwareDomainMatch - -data class Visit(val timestamp: Long, val type: VisitType) - -const val AUTOCOMPLETE_SOURCE_NAME = "memoryHistory" - -/** - * An in-memory implementation of [mozilla.components.concept.storage.HistoryStorage]. - */ -class InMemoryHistoryStorage : HistoryStorage { - @VisibleForTesting - internal var pages: HashMap> = linkedMapOf() - @VisibleForTesting - internal val pageMeta: HashMap = hashMapOf() - - override suspend fun warmUp() { - // No-op for an in-memory store - } - - override suspend fun recordVisit(uri: String, visit: PageVisit) { - val now = System.currentTimeMillis() - if (visit.redirectSource != RedirectSource.NOT_A_SOURCE) { - return - } - - synchronized(pages) { - if (!pages.containsKey(uri)) { - pages[uri] = mutableListOf(Visit(now, visit.visitType)) - } else { - pages[uri]!!.add(Visit(now, visit.visitType)) - } - } - } - - override suspend fun recordObservation(uri: String, observation: PageObservation) = synchronized(pageMeta) { - val existingPageObservation = pageMeta[uri] - - if (existingPageObservation == null || - (!observation.title.isNullOrEmpty() && !observation.previewImageUrl.isNullOrEmpty()) - ) { - pageMeta[uri] = observation - } else if (!observation.title.isNullOrEmpty()) { - // Carryover the existing observed previewImageUrl - pageMeta[uri] = observation.copy(previewImageUrl = existingPageObservation.previewImageUrl) - } else { - // Carryover the existing observed title - pageMeta[uri] = observation.copy(title = existingPageObservation.title) - } - } - - override suspend fun getVisited(uris: List): List = synchronized(pages) { - return uris.map { - if (pages[it] != null && pages[it]!!.size > 0) { - return@map true - } - return@map false - } - } - - override suspend fun getVisited(): List = synchronized(pages) { - return pages.keys.toList() - } - - override suspend fun getVisitsPaginated(offset: Long, count: Long, excludeTypes: List): List { - throw UnsupportedOperationException("Pagination is not yet supported by the in-memory history storage") - } - - override suspend fun getDetailedVisits( - start: Long, - end: Long, - excludeTypes: List - ): List = synchronized(pages + pageMeta) { - val visits = mutableListOf() - - pages.forEach { - it.value.forEach { visit -> - if (visit.timestamp in start..end && !excludeTypes.contains(visit.type)) { - visits.add( - VisitInfo( - url = it.key, - title = pageMeta[it.key]?.title, - visitTime = visit.timestamp, - visitType = visit.type, - previewImageUrl = pageMeta[it.key]?.previewImageUrl - ) - ) - } - } - } - - return visits - } - - override suspend fun getTopFrecentSites( - numItems: Int, - frecencyThreshold: FrecencyThresholdOption - ): List { - throw UnsupportedOperationException("getTopFrecentSites is not yet supported by the in-memory history storage") - } - - override fun getSuggestions(query: String, limit: Int): List = synchronized(pages + pageMeta) { - data class Hit(val url: String, val score: Int) - - val urlMatches = pages.asSequence().map { - Hit(it.key, levenshteinDistance(it.key, query)) - } - val titleMatches = pageMeta.asSequence().map { - Hit(it.key, levenshteinDistance(it.value.title ?: "", query)) - } - val matchedUrls = mutableMapOf() - urlMatches.plus(titleMatches).forEach { - if (matchedUrls.containsKey(it.url) && matchedUrls[it.url]!! < it.score) { - matchedUrls[it.url] = it.score - } else { - matchedUrls[it.url] = it.score - } - } - // Calculate maxScore so that we can invert our scoring. - // Lower Levenshtein distance should produce a higher score. - val maxScore = urlMatches.maxByOrNull { it.score }?.score ?: return@synchronized listOf() - - // TODO exclude non-matching results entirely? Score that implies complete mismatch. - matchedUrls.asSequence().sortedBy { it.value }.map { - SearchResult(id = it.key, score = maxScore - it.value, url = it.key, title = pageMeta[it.key]?.title) - }.take(limit).toList() - } - - override fun getAutocompleteSuggestion(query: String): HistoryAutocompleteResult? = synchronized(pages) { - return segmentAwareDomainMatch(query, pages.keys)?.let { urlMatch -> - HistoryAutocompleteResult( - query, urlMatch.matchedSegment, urlMatch.url, AUTOCOMPLETE_SOURCE_NAME, pages.size - ) - } - } - - override suspend fun deleteEverything() = synchronized(pages + pageMeta) { - pages.clear() - pageMeta.clear() - } - - override suspend fun deleteVisitsSince(since: Long) = synchronized(pages) { - pages.entries.forEach { - it.setValue(it.value.filterNot { visit -> visit.timestamp >= since }.toMutableList()) - } - pages = pages.filter { it.value.isNotEmpty() } as HashMap> - } - - override suspend fun deleteVisitsBetween(startTime: Long, endTime: Long) = synchronized(pages) { - pages.entries.forEach { - it.setValue( - it.value.filterNot { visit -> - visit.timestamp >= startTime && visit.timestamp <= endTime - }.toMutableList() - ) - } - pages = pages.filter { it.value.isNotEmpty() } as HashMap> - } - - override suspend fun deleteVisitsFor(url: String) = synchronized(pages + pageMeta) { - pages.remove(url) - pageMeta.remove(url) - Unit - } - - override suspend fun deleteVisit(url: String, timestamp: Long) = synchronized(pages) { - if (pages.containsKey(url)) { - pages[url] = pages[url]!!.filter { it.timestamp != timestamp }.toMutableList() - } - } - - override suspend fun prune() { - // Not applicable. - } - - override suspend fun runMaintenance() { - // Not applicable. - } - - override fun cleanup() { - // GC will take care of our internal data structures, so there's nothing to do here. - } -} diff --git a/components/browser/storage-memory/src/test/java/mozilla/components/browser/storage/memory/InMemoryHistoryStorageTest.kt b/components/browser/storage-memory/src/test/java/mozilla/components/browser/storage/memory/InMemoryHistoryStorageTest.kt deleted file mode 100644 index 8c5f358da93..00000000000 --- a/components/browser/storage-memory/src/test/java/mozilla/components/browser/storage/memory/InMemoryHistoryStorageTest.kt +++ /dev/null @@ -1,401 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package mozilla.components.browser.storage.memory - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.runBlocking -import mozilla.components.concept.storage.PageObservation -import mozilla.components.concept.storage.PageVisit -import mozilla.components.concept.storage.RedirectSource -import mozilla.components.concept.storage.VisitType -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Test -import org.junit.runner.RunWith -import java.lang.Thread.sleep - -@RunWith(AndroidJUnit4::class) -class InMemoryHistoryStorageTest { - - @Test - fun `store can be used to track visit information`() = runBlocking { - val history = InMemoryHistoryStorage() - - assertEquals(0, history.pages.size) - - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - assertEquals(1, history.pages.size) - assertEquals(1, history.pages["http://www.mozilla.org"]!!.size) - assertEquals(VisitType.LINK, history.pages["http://www.mozilla.org"]!![0].type) - - // Reloads are recorded. - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.RELOAD, RedirectSource.NOT_A_SOURCE)) - assertEquals(1, history.pages.size) - assertEquals(2, history.pages["http://www.mozilla.org"]!!.size) - assertEquals(VisitType.LINK, history.pages["http://www.mozilla.org"]!![0].type) - assertEquals(VisitType.RELOAD, history.pages["http://www.mozilla.org"]!![1].type) - - // Visits for multiple pages are tracked. - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - assertEquals(2, history.pages.size) - assertEquals(2, history.pages["http://www.mozilla.org"]!!.size) - assertEquals(VisitType.LINK, history.pages["http://www.mozilla.org"]!![0].type) - assertEquals(VisitType.RELOAD, history.pages["http://www.mozilla.org"]!![1].type) - assertEquals(1, history.pages["http://www.firefox.com"]!!.size) - assertEquals(VisitType.LINK, history.pages["http://www.firefox.com"]!![0].type) - } - - @Test - fun `store can be used to query detailed visit information`() = runBlocking { - val history = InMemoryHistoryStorage() - - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.RELOAD, RedirectSource.NOT_A_SOURCE)) - history.recordObservation( - "http://www.mozilla.org", - PageObservation("Mozilla", "https://test.com/og-image-url") - ) - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.REDIRECT_TEMPORARY, RedirectSource.NOT_A_SOURCE)) - - val visits = history.getDetailedVisits(0, excludeTypes = listOf(VisitType.REDIRECT_TEMPORARY)) - assertEquals(3, visits.size) - assertEquals("http://www.mozilla.org", visits[0].url) - assertEquals("Mozilla", visits[0].title) - assertEquals("https://test.com/og-image-url", visits[0].previewImageUrl) - assertEquals(VisitType.LINK, visits[0].visitType) - - assertEquals("http://www.mozilla.org", visits[1].url) - assertEquals("Mozilla", visits[1].title) - assertEquals("https://test.com/og-image-url", visits[1].previewImageUrl) - assertEquals(VisitType.RELOAD, visits[1].visitType) - - assertEquals("http://www.firefox.com", visits[2].url) - assertEquals(null, visits[2].title) - assertEquals(VisitType.LINK, visits[2].visitType) - - val visitsAll = history.getDetailedVisits(0) - assertEquals(4, visitsAll.size) - } - - @Test - fun `store can be used to record and retrieve history via webview-style callbacks`() = runBlocking { - val history = InMemoryHistoryStorage() - - // Empty. - assertEquals(0, history.getVisited().size) - - // Regular visits are tracked. - history.recordVisit("https://www.mozilla.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - assertEquals(listOf("https://www.mozilla.org"), history.getVisited()) - - // Multiple visits can be tracked, results ordered by "URL's first seen first". - history.recordVisit("https://www.firefox.com", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - assertEquals(listOf("https://www.mozilla.org", "https://www.firefox.com"), history.getVisited()) - - // Visits marked as reloads can be tracked. - history.recordVisit("https://www.firefox.com", PageVisit(VisitType.RELOAD, RedirectSource.NOT_A_SOURCE)) - assertEquals(listOf("https://www.mozilla.org", "https://www.firefox.com"), history.getVisited()) - - // Visited urls are certainly a set. - history.recordVisit("https://www.firefox.com", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("https://www.mozilla.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("https://www.wikipedia.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - assertEquals( - listOf("https://www.mozilla.org", "https://www.firefox.com", "https://www.wikipedia.org"), - history.getVisited() - ) - } - - @Test - fun `store can be used to record and retrieve history via gecko-style callbacks`() = runBlocking { - val history = InMemoryHistoryStorage() - - assertEquals(0, history.getVisited(listOf()).size) - - // Regular visits are tracked - history.recordVisit("https://www.mozilla.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - assertEquals(listOf(true), history.getVisited(listOf("https://www.mozilla.org"))) - - // Duplicate requests are handled. - assertEquals(listOf(true, true), history.getVisited(listOf("https://www.mozilla.org", "https://www.mozilla.org"))) - - // Visit map is returned in correct order. - assertEquals(listOf(true, false), history.getVisited(listOf("https://www.mozilla.org", "https://www.unknown.com"))) - - assertEquals(listOf(false, true), history.getVisited(listOf("https://www.unknown.com", "https://www.mozilla.org"))) - - // Multiple visits can be tracked. Reloads can be tracked. - history.recordVisit("https://www.firefox.com", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("https://www.mozilla.org", PageVisit(VisitType.RELOAD, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("https://www.wikipedia.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - assertEquals(listOf(true, true, false, true), history.getVisited(listOf("https://www.firefox.com", "https://www.wikipedia.org", "https://www.unknown.com", "https://www.mozilla.org"))) - } - - @Test - fun `store can be used to track page meta information - title and previewImageUrl changes`() = runBlocking { - val history = InMemoryHistoryStorage() - assertEquals(0, history.pageMeta.size) - - // Title and previewImageUrl changes are recorded. - history.recordObservation( - "https://www.wikipedia.org", - PageObservation("Wikipedia", "https://test.com/og-image-url") - ) - assertEquals(1, history.pageMeta.size) - assertEquals( - PageObservation("Wikipedia", "https://test.com/og-image-url"), - history.pageMeta["https://www.wikipedia.org"] - ) - - history.recordObservation("https://www.wikipedia.org", PageObservation("ВикипСдия")) - assertEquals(1, history.pageMeta.size) - assertEquals( - PageObservation("ВикипСдия", "https://test.com/og-image-url"), - history.pageMeta["https://www.wikipedia.org"] - ) - - // Titles for different pages are recorded. - history.recordObservation("https://www.firefox.com", PageObservation("Firefox")) - history.recordObservation("https://www.mozilla.org", PageObservation("Мозилла")) - assertEquals(3, history.pageMeta.size) - assertEquals( - PageObservation("ВикипСдия", "https://test.com/og-image-url"), - history.pageMeta["https://www.wikipedia.org"] - ) - assertEquals(PageObservation("Firefox"), history.pageMeta["https://www.firefox.com"]) - assertEquals(PageObservation("Мозилла"), history.pageMeta["https://www.mozilla.org"]) - } - - @Test - fun `store can provide suggestions`() = runBlocking { - val history = InMemoryHistoryStorage() - assertEquals(0, history.getSuggestions("Mozilla", 100).size) - - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - val search = history.getSuggestions("Mozilla", 100) - assertEquals(1, search.size) - assertEquals("http://www.firefox.com", search[0].url) - - history.recordVisit("http://www.wikipedia.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.moscow.ru", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordObservation("http://www.mozilla.org", PageObservation("Mozilla")) - history.recordObservation("http://www.firefox.com", PageObservation("Mozilla Firefox")) - history.recordObservation("http://www.moscow.ru", PageObservation("Moscow City")) - history.recordObservation("http://www.moscow.ru/notitle", PageObservation("")) - - // Empty search. - assertEquals(5, history.getSuggestions("", 100).size) - - val search2 = history.getSuggestions("Mozilla", 100) - assertEquals(5, search2.size) - assertEquals("http://www.mozilla.org", search2[0].id) - assertEquals("http://www.mozilla.org", search2[0].url) - assertEquals("Mozilla", search2[0].title) - - assertEquals("http://www.firefox.com", search2[1].id) - assertEquals("http://www.firefox.com", search2[1].url) - assertEquals("Mozilla Firefox", search2[1].title) - - assertEquals("http://www.moscow.ru", search2[2].id) - assertEquals("http://www.moscow.ru", search2[2].url) - assertEquals("Moscow City", search2[2].title) - - assertEquals("http://www.wikipedia.org", search2[3].id) - assertEquals("http://www.wikipedia.org", search2[3].url) - assertEquals(null, search2[3].title) - } - - @Test - fun `store can provide suggestions respecting the limit`() = runBlocking { - val history = InMemoryHistoryStorage() - history.recordVisit("http://www.wikipedia.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.moscow.ru", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - history.recordObservation("http://www.mozilla.org", PageObservation("Mozilla")) - history.recordObservation("http://www.firefox.com", PageObservation("Mozilla Firefox")) - history.recordObservation("http://www.moscow.ru", PageObservation("Moscow")) - - assertEquals(3, history.getSuggestions("Mozilla", 3).size) - assertEquals(2, history.getSuggestions("Mozilla", 2).size) - assertEquals(1, history.getSuggestions("Mozilla", 1).size) - - val results = history.getSuggestions("Mozilla", 3) - assertEquals("http://www.mozilla.org", results[0].url) - assertEquals("http://www.moscow.ru", results[1].url) - assertEquals("http://www.firefox.com", results[2].url) - - val results2 = history.getSuggestions("Mozilla", 1) - assertEquals("http://www.mozilla.org", results2[0].url) - } - - @Test - fun `store can provide autocomplete suggestions`() = runBlocking { - val history = InMemoryHistoryStorage() - - assertNull(history.getAutocompleteSuggestion("moz")) - - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - var res = history.getAutocompleteSuggestion("moz")!! - assertEquals("mozilla.org", res.text) - assertEquals("http://www.mozilla.org", res.url) - assertEquals("memoryHistory", res.source) - assertEquals(1, res.totalItems) - - history.recordVisit("http://firefox.com", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - res = history.getAutocompleteSuggestion("firefox")!! - assertEquals("firefox.com", res.text) - assertEquals("http://firefox.com", res.url) - assertEquals("memoryHistory", res.source) - assertEquals(2, res.totalItems) - - history.recordVisit("https://en.wikipedia.org/wiki/Mozilla", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - res = history.getAutocompleteSuggestion("en")!! - assertEquals("en.wikipedia.org/wiki/mozilla", res.text) - assertEquals("https://en.wikipedia.org/wiki/mozilla", res.url) - assertEquals("memoryHistory", res.source) - assertEquals(3, res.totalItems) - - assertNull(history.getAutocompleteSuggestion("hello")) - } - - @Test - fun `store can delete everything`() = runBlocking { - val history = InMemoryHistoryStorage() - - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.DOWNLOAD, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.BOOKMARK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.RELOAD, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.EMBED, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.REDIRECT_PERMANENT, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.REDIRECT_TEMPORARY, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - - history.recordObservation("http://www.firefox.com", PageObservation("Firefox")) - - assertEquals(2, history.getVisited().size) - - history.deleteEverything() - - assertEquals(0, history.getVisited().size) - } - - @Test - fun `store can delete by url`() = runBlocking { - val history = InMemoryHistoryStorage() - - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.DOWNLOAD, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.BOOKMARK, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.RELOAD, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.EMBED, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.REDIRECT_PERMANENT, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.REDIRECT_TEMPORARY, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.firefox.com", PageVisit(VisitType.LINK, RedirectSource.NOT_A_SOURCE)) - - history.recordObservation("http://www.firefox.com", PageObservation("Firefox")) - - assertEquals(2, history.getVisited().size) - - history.deleteVisitsFor("http://www.mozilla.org") - - assertEquals(1, history.getVisited().size) - assertEquals("http://www.firefox.com", history.getVisited()[0]) - - history.deleteVisitsFor("http://www.firefox.com") - assertEquals(0, history.getVisited().size) - } - - @Test - fun `store can delete by 'since'`() = runBlocking { - val history = InMemoryHistoryStorage() - - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.DOWNLOAD, RedirectSource.NOT_A_SOURCE)) - history.recordVisit("http://www.mozilla.org", PageVisit(VisitType.BOOKMARK, RedirectSource.NOT_A_SOURCE)) - - history.deleteVisitsSince(0) - val visits = history.getVisited() - assertEquals(0, visits.size) - } - - @Test - fun `store can delete by 'range'`() { - val history = InMemoryHistoryStorage() - - runBlocking { - history.recordVisit("http://www.mozilla.org/1", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - sleep(10) - history.recordVisit("http://www.mozilla.org/2", PageVisit(VisitType.DOWNLOAD, RedirectSource.NOT_A_SOURCE)) - sleep(10) - history.recordVisit("http://www.mozilla.org/3", PageVisit(VisitType.BOOKMARK, RedirectSource.NOT_A_SOURCE)) - } - - val ts = runBlocking { - val visits = history.getDetailedVisits(0, Long.MAX_VALUE) - - assertEquals(3, visits.size) - visits[1].visitTime - } - - runBlocking { - history.deleteVisitsBetween(ts - 1, ts + 1) - } - val visits = runBlocking { - history.getDetailedVisits(0, Long.MAX_VALUE) - } - assertEquals(2, visits.size) - - assertEquals("http://www.mozilla.org/1", visits[0].url) - assertEquals("http://www.mozilla.org/3", visits[1].url) - } - - @Test - fun `store can delete visit by 'url' and 'timestamp'`() { - val history = InMemoryHistoryStorage() - - runBlocking { - history.recordVisit("http://www.mozilla.org/1", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - sleep(10) - history.recordVisit("http://www.mozilla.org/2", PageVisit(VisitType.DOWNLOAD, RedirectSource.NOT_A_SOURCE)) - sleep(10) - history.recordVisit("http://www.mozilla.org/3", PageVisit(VisitType.BOOKMARK, RedirectSource.NOT_A_SOURCE)) - } - - val ts = runBlocking { - val visits = history.getDetailedVisits(0, Long.MAX_VALUE) - - assertEquals(3, visits.size) - visits[1].visitTime - } - - runBlocking { - history.deleteVisit("http://www.mozilla.org/4", 111) - // There are no visits for this url, delete is a no-op. - assertEquals(3, history.getDetailedVisits(0, Long.MAX_VALUE).size) - } - - runBlocking { - history.deleteVisit("http://www.mozilla.org/1", ts) - // There is no such visit for this url, delete is a no-op. - assertEquals(3, history.getDetailedVisits(0, Long.MAX_VALUE).size) - } - - runBlocking { - history.deleteVisit("http://www.mozilla.org/2", ts) - } - - val visits = runBlocking { - history.getDetailedVisits(0, Long.MAX_VALUE) - } - assertEquals(2, visits.size) - - assertEquals("http://www.mozilla.org/1", visits[0].url) - assertEquals("http://www.mozilla.org/3", visits[1].url) - } -} diff --git a/components/browser/storage-memory/src/test/resources/robolectric.properties b/components/browser/storage-memory/src/test/resources/robolectric.properties deleted file mode 100644 index 89a6c8b4c2e..00000000000 --- a/components/browser/storage-memory/src/test/resources/robolectric.properties +++ /dev/null @@ -1 +0,0 @@ -sdk=28 \ No newline at end of file diff --git a/components/concept/storage/README.md b/components/concept/storage/README.md index e23ddcbc002..994fb16c35c 100644 --- a/components/concept/storage/README.md +++ b/components/concept/storage/README.md @@ -5,7 +5,6 @@ The `concept-storage` component contains interfaces and abstract classes that de This abstraction makes it possible to build components that work independently of the storage layer being used. Currently two store implementations are available: -- [in-memory storage](../../browser/storage-memory) - [syncable, Rust Places storage](../../browser/storage-sync) - compatible with the Firefox Sync ecosystem ## Usage diff --git a/components/feature/awesomebar/build.gradle b/components/feature/awesomebar/build.gradle index d3858870aa7..4a2cced89d0 100644 --- a/components/feature/awesomebar/build.gradle +++ b/components/feature/awesomebar/build.gradle @@ -42,7 +42,6 @@ dependencies { implementation Dependencies.kotlin_stdlib testImplementation project(':support-test') - testImplementation project(':browser-storage-memory') testImplementation project(':lib-fetch-httpurlconnection') testImplementation Dependencies.androidx_test_core diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt index 59fbbe0d04f..5663769fc34 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt @@ -6,18 +6,12 @@ package mozilla.components.feature.awesomebar.provider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking -import mozilla.components.browser.storage.memory.InMemoryHistoryStorage -import mozilla.components.concept.storage.DocumentType -import mozilla.components.concept.storage.HistoryMetadata -import mozilla.components.concept.storage.HistoryMetadataKey -import mozilla.components.concept.storage.HistoryMetadataStorage -import mozilla.components.concept.storage.PageVisit -import mozilla.components.concept.storage.RedirectSource -import mozilla.components.concept.storage.SearchResult -import mozilla.components.concept.storage.VisitType +import mozilla.components.concept.storage.* import mozilla.components.support.test.eq import mozilla.components.support.test.mock import mozilla.components.support.test.whenever +import mozilla.components.support.utils.StorageUtils +import mozilla.components.support.utils.segmentAwareDomainMatch import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test @@ -27,6 +21,184 @@ import org.mockito.Mockito.doReturn @RunWith(AndroidJUnit4::class) class CombinedHistorySuggestionProviderTest { + + class TestingHistoryStorage : HistoryStorage { + + data class Visit(val timestamp: Long, val type: VisitType) + + val AUTOCOMPLETE_SOURCE_NAME = "memoryHistory" + + var pages: HashMap> = linkedMapOf() + val pageMeta: HashMap = hashMapOf() + + override suspend fun warmUp() { + // No-op for an in-memory store + } + + override suspend fun recordVisit(uri: String, visit: PageVisit) { + val now = System.currentTimeMillis() + if (visit.redirectSource != RedirectSource.NOT_A_SOURCE) { + return + } + + synchronized(pages) { + if (!pages.containsKey(uri)) { + pages[uri] = mutableListOf(Visit(now, visit.visitType)) + } else { + pages[uri]!!.add(Visit(now, visit.visitType)) + } + } + } + + override suspend fun recordObservation(uri: String, observation: PageObservation) = synchronized(pageMeta) { + val existingPageObservation = pageMeta[uri] + + if (existingPageObservation == null || + (!observation.title.isNullOrEmpty() && !observation.previewImageUrl.isNullOrEmpty()) + ) { + pageMeta[uri] = observation + } else if (!observation.title.isNullOrEmpty()) { + // Carryover the existing observed previewImageUrl + pageMeta[uri] = observation.copy(previewImageUrl = existingPageObservation.previewImageUrl) + } else { + // Carryover the existing observed title + pageMeta[uri] = observation.copy(title = existingPageObservation.title) + } + } + + override suspend fun getVisited(uris: List): List = synchronized(pages) { + return uris.map { + if (pages[it] != null && pages[it]!!.size > 0) { + return@map true + } + return@map false + } + } + + override suspend fun getVisited(): List = synchronized(pages) { + return pages.keys.toList() + } + + override suspend fun getVisitsPaginated(offset: Long, count: Long, excludeTypes: List): List { + throw UnsupportedOperationException("Pagination is not yet supported by the in-memory history storage") + } + + override suspend fun getDetailedVisits( + start: Long, + end: Long, + excludeTypes: List + ): List = synchronized(pages + pageMeta) { + val visits = mutableListOf() + + pages.forEach { + it.value.forEach { visit -> + if (visit.timestamp in start..end && !excludeTypes.contains(visit.type)) { + visits.add( + VisitInfo( + url = it.key, + title = pageMeta[it.key]?.title, + visitTime = visit.timestamp, + visitType = visit.type, + previewImageUrl = pageMeta[it.key]?.previewImageUrl + ) + ) + } + } + } + + return visits + } + + override suspend fun getTopFrecentSites( + numItems: Int, + frecencyThreshold: FrecencyThresholdOption + ): List { + throw UnsupportedOperationException("getTopFrecentSites is not yet supported by the in-memory history storage") + } + + override fun getSuggestions(query: String, limit: Int): List = synchronized(pages + pageMeta) { + data class Hit(val url: String, val score: Int) + + val urlMatches = pages.asSequence().map { + Hit(it.key, StorageUtils.levenshteinDistance(it.key, query)) + } + val titleMatches = pageMeta.asSequence().map { + Hit(it.key, StorageUtils.levenshteinDistance(it.value.title ?: "", query)) + } + val matchedUrls = mutableMapOf() + urlMatches.plus(titleMatches).forEach { + if (matchedUrls.containsKey(it.url) && matchedUrls[it.url]!! < it.score) { + matchedUrls[it.url] = it.score + } else { + matchedUrls[it.url] = it.score + } + } + // Calculate maxScore so that we can invert our scoring. + // Lower Levenshtein distance should produce a higher score. + val maxScore = urlMatches.maxByOrNull { it.score }?.score ?: return@synchronized listOf() + + matchedUrls.asSequence().sortedBy { it.value }.map { + SearchResult(id = it.key, score = maxScore - it.value, url = it.key, title = pageMeta[it.key]?.title) + }.take(limit).toList() + } + + override fun getAutocompleteSuggestion(query: String): HistoryAutocompleteResult? = synchronized(pages) { + return segmentAwareDomainMatch(query, pages.keys)?.let { urlMatch -> + HistoryAutocompleteResult( + query, urlMatch.matchedSegment, urlMatch.url, AUTOCOMPLETE_SOURCE_NAME, pages.size + ) + } + } + + override suspend fun deleteEverything() = synchronized(pages + pageMeta) { + pages.clear() + pageMeta.clear() + } + + override suspend fun deleteVisitsSince(since: Long) = synchronized(pages) { + pages.entries.forEach { + it.setValue(it.value.filterNot { visit -> visit.timestamp >= since }.toMutableList()) + } + pages = pages.filter { it.value.isNotEmpty() } as HashMap> + } + + override suspend fun deleteVisitsBetween(startTime: Long, endTime: Long) = synchronized(pages) { + pages.entries.forEach { + it.setValue( + it.value.filterNot { visit -> + visit.timestamp >= startTime && visit.timestamp <= endTime + }.toMutableList() + ) + } + pages = pages.filter { it.value.isNotEmpty() } as HashMap> + } + + override suspend fun deleteVisitsFor(url: String) = synchronized(pages + pageMeta) { + pages.remove(url) + pageMeta.remove(url) + Unit + } + + override suspend fun deleteVisit(url: String, timestamp: Long) = synchronized(pages) { + if (pages.containsKey(url)) { + pages[url] = pages[url]!!.filter { it.timestamp != timestamp }.toMutableList() + } + } + + override suspend fun prune() { + // Not applicable. + } + + override suspend fun runMaintenance() { + // Not applicable. + } + + override fun cleanup() { + // GC will take care of our internal data structures, so there's nothing to do here. + } + } + + private val historyEntry = HistoryMetadata( key = HistoryMetadataKey("http://www.mozilla.com", null, null), title = "mozilla", @@ -41,7 +213,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN history items exists WHEN onInputChanged is called with empty text THEN return empty suggestions list`() = runBlocking { val storage: HistoryMetadataStorage = mock() whenever(storage.queryHistoryMetadata("moz", DEFAULT_METADATA_SUGGESTION_LIMIT)).thenReturn(listOf(historyEntry)) - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() history.recordVisit("http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) val provider = CombinedHistorySuggestionProvider(history, storage, mock()) @@ -53,7 +225,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN more suggestions asked than metadata items exist WHEN user changes input THEN return a combined list of suggestions`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() history.recordVisit("http://www.mozilla.com/firefox", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) val provider = CombinedHistorySuggestionProvider(history, storage, mock()) @@ -68,7 +240,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN fewer suggestions asked than metadata items exist WHEN user changes input THEN return suggestions only based on metadata items`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() history.recordVisit("http://www.mozilla.com/firefox", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) val provider = CombinedHistorySuggestionProvider(history, storage, mock(), maxNumberOfSuggestions = 1) @@ -82,7 +254,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN only storage history items exist WHEN user changes input THEN return suggestions only based on storage items`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(emptyList()).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() history.recordVisit("http://www.mozilla.com/firefox", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) val provider = CombinedHistorySuggestionProvider(history, storage, mock(), maxNumberOfSuggestions = 1) @@ -96,7 +268,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN duplicated metadata and storage entries WHEN user changes input THEN return distinct suggestions`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() history.recordVisit( "http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE) @@ -146,7 +318,7 @@ class CombinedHistorySuggestionProviderTest { ) val metadataStorage: HistoryMetadataStorage = mock() - val historyStorage: InMemoryHistoryStorage = mock() + val historyStorage: TestingHistoryStorage = mock() doReturn(listOf(metadataEntry2, metadataEntry1)).`when`(metadataStorage).queryHistoryMetadata(eq("moz"), anyInt()) doReturn(listOf(searchResult1, searchResult2)).`when`(historyStorage).getSuggestions(eq("moz"), anyInt()) diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt index 78d06bd3fd1..86676d36952 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt @@ -5,13 +5,8 @@ package mozilla.components.feature.awesomebar.provider import kotlinx.coroutines.runBlocking -import mozilla.components.browser.storage.memory.InMemoryHistoryStorage import mozilla.components.concept.engine.Engine -import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.concept.storage.PageVisit -import mozilla.components.concept.storage.RedirectSource -import mozilla.components.concept.storage.SearchResult -import mozilla.components.concept.storage.VisitType +import mozilla.components.concept.storage.* import mozilla.components.feature.awesomebar.facts.AwesomeBarFacts import mozilla.components.support.base.Component import mozilla.components.support.base.facts.Action @@ -20,18 +15,193 @@ import mozilla.components.support.base.facts.FactProcessor import mozilla.components.support.base.facts.Facts import mozilla.components.support.test.eq import mozilla.components.support.test.mock +import mozilla.components.support.utils.StorageUtils +import mozilla.components.support.utils.segmentAwareDomainMatch import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito.`when` -import org.mockito.Mockito.never -import org.mockito.Mockito.times -import org.mockito.Mockito.verify +import org.mockito.Mockito.* class HistoryStorageSuggestionProviderTest { + class TestingHistoryStorage : HistoryStorage { + + data class Visit(val timestamp: Long, val type: VisitType) + + val AUTOCOMPLETE_SOURCE_NAME = "memoryHistory" + + var pages: HashMap> = linkedMapOf() + val pageMeta: HashMap = hashMapOf() + + override suspend fun warmUp() { + // No-op for an in-memory store + } + + override suspend fun recordVisit(uri: String, visit: PageVisit) { + val now = System.currentTimeMillis() + if (visit.redirectSource != RedirectSource.NOT_A_SOURCE) { + return + } + + synchronized(pages) { + if (!pages.containsKey(uri)) { + pages[uri] = mutableListOf(Visit(now, visit.visitType)) + } else { + pages[uri]!!.add(Visit(now, visit.visitType)) + } + } + } + + override suspend fun recordObservation(uri: String, observation: PageObservation) = synchronized(pageMeta) { + val existingPageObservation = pageMeta[uri] + + if (existingPageObservation == null || + (!observation.title.isNullOrEmpty() && !observation.previewImageUrl.isNullOrEmpty()) + ) { + pageMeta[uri] = observation + } else if (!observation.title.isNullOrEmpty()) { + // Carryover the existing observed previewImageUrl + pageMeta[uri] = observation.copy(previewImageUrl = existingPageObservation.previewImageUrl) + } else { + // Carryover the existing observed title + pageMeta[uri] = observation.copy(title = existingPageObservation.title) + } + } + + override suspend fun getVisited(uris: List): List = synchronized(pages) { + return uris.map { + if (pages[it] != null && pages[it]!!.size > 0) { + return@map true + } + return@map false + } + } + + override suspend fun getVisited(): List = synchronized(pages) { + return pages.keys.toList() + } + + override suspend fun getVisitsPaginated(offset: Long, count: Long, excludeTypes: List): List { + throw UnsupportedOperationException("Pagination is not yet supported by the in-memory history storage") + } + + override suspend fun getDetailedVisits( + start: Long, + end: Long, + excludeTypes: List + ): List = synchronized(pages + pageMeta) { + val visits = mutableListOf() + + pages.forEach { + it.value.forEach { visit -> + if (visit.timestamp in start..end && !excludeTypes.contains(visit.type)) { + visits.add( + VisitInfo( + url = it.key, + title = pageMeta[it.key]?.title, + visitTime = visit.timestamp, + visitType = visit.type, + previewImageUrl = pageMeta[it.key]?.previewImageUrl + ) + ) + } + } + } + + return visits + } + + override suspend fun getTopFrecentSites( + numItems: Int, + frecencyThreshold: FrecencyThresholdOption + ): List { + throw UnsupportedOperationException("getTopFrecentSites is not yet supported by the in-memory history storage") + } + + override fun getSuggestions(query: String, limit: Int): List = synchronized(pages + pageMeta) { + data class Hit(val url: String, val score: Int) + + val urlMatches = pages.asSequence().map { + Hit(it.key, StorageUtils.levenshteinDistance(it.key, query)) + } + val titleMatches = pageMeta.asSequence().map { + Hit(it.key, StorageUtils.levenshteinDistance(it.value.title ?: "", query)) + } + val matchedUrls = mutableMapOf() + urlMatches.plus(titleMatches).forEach { + if (matchedUrls.containsKey(it.url) && matchedUrls[it.url]!! < it.score) { + matchedUrls[it.url] = it.score + } else { + matchedUrls[it.url] = it.score + } + } + // Calculate maxScore so that we can invert our scoring. + // Lower Levenshtein distance should produce a higher score. + val maxScore = urlMatches.maxByOrNull { it.score }?.score ?: return@synchronized listOf() + + matchedUrls.asSequence().sortedBy { it.value }.map { + SearchResult(id = it.key, score = maxScore - it.value, url = it.key, title = pageMeta[it.key]?.title) + }.take(limit).toList() + } + + override fun getAutocompleteSuggestion(query: String): HistoryAutocompleteResult? = synchronized(pages) { + return segmentAwareDomainMatch(query, pages.keys)?.let { urlMatch -> + HistoryAutocompleteResult( + query, urlMatch.matchedSegment, urlMatch.url, AUTOCOMPLETE_SOURCE_NAME, pages.size + ) + } + } + + override suspend fun deleteEverything() = synchronized(pages + pageMeta) { + pages.clear() + pageMeta.clear() + } + + override suspend fun deleteVisitsSince(since: Long) = synchronized(pages) { + pages.entries.forEach { + it.setValue(it.value.filterNot { visit -> visit.timestamp >= since }.toMutableList()) + } + pages = pages.filter { it.value.isNotEmpty() } as HashMap> + } + + override suspend fun deleteVisitsBetween(startTime: Long, endTime: Long) = synchronized(pages) { + pages.entries.forEach { + it.setValue( + it.value.filterNot { visit -> + visit.timestamp >= startTime && visit.timestamp <= endTime + }.toMutableList() + ) + } + pages = pages.filter { it.value.isNotEmpty() } as HashMap> + } + + override suspend fun deleteVisitsFor(url: String) = synchronized(pages + pageMeta) { + pages.remove(url) + pageMeta.remove(url) + Unit + } + + override suspend fun deleteVisit(url: String, timestamp: Long) = synchronized(pages) { + if (pages.containsKey(url)) { + pages[url] = pages[url]!!.filter { it.timestamp != timestamp }.toMutableList() + } + } + + override suspend fun prune() { + // Not applicable. + } + + override suspend fun runMaintenance() { + // Not applicable. + } + + override fun cleanup() { + // GC will take care of our internal data structures, so there's nothing to do here. + } + } + @Before fun setup() { Facts.clearProcessors() @@ -47,7 +217,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider returns suggestions from configured history storage`() = runBlocking { - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() val provider = HistoryStorageSuggestionProvider(history, mock()) history.recordVisit("http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) @@ -59,7 +229,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider limits number of returned suggestions to 20 by default`() = runBlocking { - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() val provider = HistoryStorageSuggestionProvider(history, mock()) for (i in 1..100) { @@ -72,7 +242,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider allows lowering the number of returned suggestions beneath the default`() = runBlocking { - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() val provider = HistoryStorageSuggestionProvider( historyStorage = history, loadUrlUseCase = mock(), maxNumberOfSuggestions = 2 ) @@ -87,7 +257,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider allows increasing the number of returned suggestions above the default`() = runBlocking { - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() val provider = HistoryStorageSuggestionProvider( historyStorage = history, loadUrlUseCase = mock(), maxNumberOfSuggestions = 22 ) @@ -141,7 +311,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `provider calls speculative connect for URL of highest scored suggestion`() = runBlocking { - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() val engine: Engine = mock() val provider = HistoryStorageSuggestionProvider(history, mock(), engine = engine) @@ -159,7 +329,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `fact is emitted when suggestion is clicked`() = runBlocking { - val history = InMemoryHistoryStorage() + val history = TestingHistoryStorage() val engine: Engine = mock() val provider = HistoryStorageSuggestionProvider(history, mock(), engine = engine) diff --git a/components/feature/toolbar/build.gradle b/components/feature/toolbar/build.gradle index 5069f62fb27..2edb3b89e36 100644 --- a/components/feature/toolbar/build.gradle +++ b/components/feature/toolbar/build.gradle @@ -44,7 +44,6 @@ dependencies { implementation Dependencies.kotlin_coroutines testImplementation project(':support-test') - testImplementation project(':browser-storage-memory') testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt index 82900eecfe6..d57674f2ec5 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt @@ -9,12 +9,8 @@ import kotlinx.coroutines.runBlocking import mozilla.components.browser.domains.Domain import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.domains.autocomplete.DomainList -import mozilla.components.browser.storage.memory.InMemoryHistoryStorage import mozilla.components.concept.engine.Engine -import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.concept.storage.PageVisit -import mozilla.components.concept.storage.RedirectSource -import mozilla.components.concept.storage.VisitType +import mozilla.components.concept.storage.* import mozilla.components.concept.toolbar.AutocompleteDelegate import mozilla.components.concept.toolbar.AutocompleteResult import mozilla.components.concept.toolbar.Toolbar @@ -22,6 +18,8 @@ import mozilla.components.support.test.any import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.eq import mozilla.components.support.test.mock +import mozilla.components.support.utils.StorageUtils.levenshteinDistance +import mozilla.components.support.utils.segmentAwareDomainMatch import org.junit.Assert.assertNotNull import org.junit.Assert.fail import org.junit.Test @@ -131,6 +129,182 @@ class ToolbarAutocompleteFeatureTest { } } + class TestingHistoryStorage : HistoryStorage { + + data class Visit(val timestamp: Long, val type: VisitType) + + val AUTOCOMPLETE_SOURCE_NAME = "memoryHistory" + + var pages: HashMap> = linkedMapOf() + val pageMeta: HashMap = hashMapOf() + + override suspend fun warmUp() { + // No-op for an in-memory store + } + + override suspend fun recordVisit(uri: String, visit: PageVisit) { + val now = System.currentTimeMillis() + if (visit.redirectSource != RedirectSource.NOT_A_SOURCE) { + return + } + + synchronized(pages) { + if (!pages.containsKey(uri)) { + pages[uri] = mutableListOf(Visit(now, visit.visitType)) + } else { + pages[uri]!!.add(Visit(now, visit.visitType)) + } + } + } + + override suspend fun recordObservation(uri: String, observation: PageObservation) = synchronized(pageMeta) { + val existingPageObservation = pageMeta[uri] + + if (existingPageObservation == null || + (!observation.title.isNullOrEmpty() && !observation.previewImageUrl.isNullOrEmpty()) + ) { + pageMeta[uri] = observation + } else if (!observation.title.isNullOrEmpty()) { + // Carryover the existing observed previewImageUrl + pageMeta[uri] = observation.copy(previewImageUrl = existingPageObservation.previewImageUrl) + } else { + // Carryover the existing observed title + pageMeta[uri] = observation.copy(title = existingPageObservation.title) + } + } + + override suspend fun getVisited(uris: List): List = synchronized(pages) { + return uris.map { + if (pages[it] != null && pages[it]!!.size > 0) { + return@map true + } + return@map false + } + } + + override suspend fun getVisited(): List = synchronized(pages) { + return pages.keys.toList() + } + + override suspend fun getVisitsPaginated(offset: Long, count: Long, excludeTypes: List): List { + throw UnsupportedOperationException("Pagination is not yet supported by the in-memory history storage") + } + + override suspend fun getDetailedVisits( + start: Long, + end: Long, + excludeTypes: List + ): List = synchronized(pages + pageMeta) { + val visits = mutableListOf() + + pages.forEach { + it.value.forEach { visit -> + if (visit.timestamp in start..end && !excludeTypes.contains(visit.type)) { + visits.add( + VisitInfo( + url = it.key, + title = pageMeta[it.key]?.title, + visitTime = visit.timestamp, + visitType = visit.type, + previewImageUrl = pageMeta[it.key]?.previewImageUrl + ) + ) + } + } + } + + return visits + } + + override suspend fun getTopFrecentSites( + numItems: Int, + frecencyThreshold: FrecencyThresholdOption + ): List { + throw UnsupportedOperationException("getTopFrecentSites is not yet supported by the in-memory history storage") + } + + override fun getSuggestions(query: String, limit: Int): List = synchronized(pages + pageMeta) { + data class Hit(val url: String, val score: Int) + + val urlMatches = pages.asSequence().map { + Hit(it.key, levenshteinDistance(it.key, query)) + } + val titleMatches = pageMeta.asSequence().map { + Hit(it.key, levenshteinDistance(it.value.title ?: "", query)) + } + val matchedUrls = mutableMapOf() + urlMatches.plus(titleMatches).forEach { + if (matchedUrls.containsKey(it.url) && matchedUrls[it.url]!! < it.score) { + matchedUrls[it.url] = it.score + } else { + matchedUrls[it.url] = it.score + } + } + // Calculate maxScore so that we can invert our scoring. + // Lower Levenshtein distance should produce a higher score. + val maxScore = urlMatches.maxByOrNull { it.score }?.score ?: return@synchronized listOf() + + matchedUrls.asSequence().sortedBy { it.value }.map { + SearchResult(id = it.key, score = maxScore - it.value, url = it.key, title = pageMeta[it.key]?.title) + }.take(limit).toList() + } + + override fun getAutocompleteSuggestion(query: String): HistoryAutocompleteResult? = synchronized(pages) { + return segmentAwareDomainMatch(query, pages.keys)?.let { urlMatch -> + HistoryAutocompleteResult( + query, urlMatch.matchedSegment, urlMatch.url, AUTOCOMPLETE_SOURCE_NAME, pages.size + ) + } + } + + override suspend fun deleteEverything() = synchronized(pages + pageMeta) { + pages.clear() + pageMeta.clear() + } + + override suspend fun deleteVisitsSince(since: Long) = synchronized(pages) { + pages.entries.forEach { + it.setValue(it.value.filterNot { visit -> visit.timestamp >= since }.toMutableList()) + } + pages = pages.filter { it.value.isNotEmpty() } as HashMap> + } + + override suspend fun deleteVisitsBetween(startTime: Long, endTime: Long) = synchronized(pages) { + pages.entries.forEach { + it.setValue( + it.value.filterNot { visit -> + visit.timestamp >= startTime && visit.timestamp <= endTime + }.toMutableList() + ) + } + pages = pages.filter { it.value.isNotEmpty() } as HashMap> + } + + override suspend fun deleteVisitsFor(url: String) = synchronized(pages + pageMeta) { + pages.remove(url) + pageMeta.remove(url) + Unit + } + + override suspend fun deleteVisit(url: String, timestamp: Long) = synchronized(pages) { + if (pages.containsKey(url)) { + pages[url] = pages[url]!!.filter { it.timestamp != timestamp }.toMutableList() + } + } + + override suspend fun prune() { + // Not applicable. + } + + override suspend fun runMaintenance() { + // Not applicable. + } + + override fun cleanup() { + // GC will take care of our internal data structures, so there's nothing to do here. + } + } + @Test fun `feature can be used without providers`() { val toolbar = TestToolbar() @@ -153,7 +327,7 @@ class ToolbarAutocompleteFeatureTest { var feature = ToolbarAutocompleteFeature(toolbar) val autocompleteDelegate: AutocompleteDelegate = mock() - var history: HistoryStorage = InMemoryHistoryStorage() + var history: HistoryStorage = TestingHistoryStorage() val domains = object : BaseDomainAutocompleteProvider(DomainList.CUSTOM, { emptyList() }) { fun testDomains(list: List) { domains = list @@ -206,7 +380,7 @@ class ToolbarAutocompleteFeatureTest { ) // Can autocomplete with empty history and domain providers. - history = InMemoryHistoryStorage() + history = TestingHistoryStorage() domains.testDomains(listOf()) feature.addHistoryStorageProvider(history) diff --git a/taskcluster/ci/config.yml b/taskcluster/ci/config.yml index 649b7a1c6e2..8dc7d1dbd72 100644 --- a/taskcluster/ci/config.yml +++ b/taskcluster/ci/config.yml @@ -17,7 +17,6 @@ treeherder: browser-menu2: browser-menu2 browser-session-storage: browser-session-storage browser-state: browser-state - browser-storage-memory: browser-storage-memory browser-storage-sync: browser-storage-sync browser-tabstray: browser-tabstray browser-thumbnails: browser-thumbnails From d596b8229caab1b030fb79e108d79e5dea685e19 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Tue, 2 Nov 2021 23:54:07 +0530 Subject: [PATCH 2/9] lint fixes --- .../CombinedHistorySuggestionProviderTest.kt | 31 ++++++++++++------- .../HistoryStorageSuggestionProviderTest.kt | 16 ++++++++-- .../toolbar/ToolbarAutocompleteFeatureTest.kt | 11 ++++++- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt index 5663769fc34..3cb153cab43 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt @@ -6,7 +6,20 @@ package mozilla.components.feature.awesomebar.provider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking -import mozilla.components.concept.storage.* +import mozilla.components.concept.storage.DocumentType +import mozilla.components.concept.storage.FrecencyThresholdOption +import mozilla.components.concept.storage.HistoryAutocompleteResult +import mozilla.components.concept.storage.HistoryMetadata +import mozilla.components.concept.storage.HistoryMetadataKey +import mozilla.components.concept.storage.HistoryMetadataStorage +import mozilla.components.concept.storage.HistoryStorage +import mozilla.components.concept.storage.PageObservation +import mozilla.components.concept.storage.PageVisit +import mozilla.components.concept.storage.RedirectSource +import mozilla.components.concept.storage.SearchResult +import mozilla.components.concept.storage.TopFrecentSiteInfo +import mozilla.components.concept.storage.VisitInfo +import mozilla.components.concept.storage.VisitType import mozilla.components.support.test.eq import mozilla.components.support.test.mock import mozilla.components.support.test.whenever @@ -185,20 +198,16 @@ class CombinedHistorySuggestionProviderTest { } } - override suspend fun prune() { - // Not applicable. - } + // Not applicable. + override suspend fun prune() {} - override suspend fun runMaintenance() { - // Not applicable. - } + // Not applicable. + override suspend fun runMaintenance() {} - override fun cleanup() { - // GC will take care of our internal data structures, so there's nothing to do here. - } + // GC will take care of our internal data structures, so there's nothing to do here. + override fun cleanup() {} } - private val historyEntry = HistoryMetadata( key = HistoryMetadataKey("http://www.mozilla.com", null, null), title = "mozilla", diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt index 86676d36952..ac4fb53566e 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt @@ -6,7 +6,16 @@ package mozilla.components.feature.awesomebar.provider import kotlinx.coroutines.runBlocking import mozilla.components.concept.engine.Engine -import mozilla.components.concept.storage.* +import mozilla.components.concept.storage.FrecencyThresholdOption +import mozilla.components.concept.storage.HistoryAutocompleteResult +import mozilla.components.concept.storage.HistoryStorage +import mozilla.components.concept.storage.PageObservation +import mozilla.components.concept.storage.PageVisit +import mozilla.components.concept.storage.RedirectSource +import mozilla.components.concept.storage.SearchResult +import mozilla.components.concept.storage.TopFrecentSiteInfo +import mozilla.components.concept.storage.VisitInfo +import mozilla.components.concept.storage.VisitType import mozilla.components.feature.awesomebar.facts.AwesomeBarFacts import mozilla.components.support.base.Component import mozilla.components.support.base.facts.Action @@ -22,7 +31,10 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito.* +import org.mockito.Mockito.`when` +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify class HistoryStorageSuggestionProviderTest { diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt index d57674f2ec5..e721aa370e3 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt @@ -10,7 +10,16 @@ import mozilla.components.browser.domains.Domain import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.domains.autocomplete.DomainList import mozilla.components.concept.engine.Engine -import mozilla.components.concept.storage.* +import mozilla.components.concept.storage.FrecencyThresholdOption +import mozilla.components.concept.storage.HistoryAutocompleteResult +import mozilla.components.concept.storage.HistoryStorage +import mozilla.components.concept.storage.PageObservation +import mozilla.components.concept.storage.PageVisit +import mozilla.components.concept.storage.RedirectSource +import mozilla.components.concept.storage.SearchResult +import mozilla.components.concept.storage.TopFrecentSiteInfo +import mozilla.components.concept.storage.VisitInfo +import mozilla.components.concept.storage.VisitType import mozilla.components.concept.toolbar.AutocompleteDelegate import mozilla.components.concept.toolbar.AutocompleteResult import mozilla.components.concept.toolbar.Toolbar From 00c03a453f18ee8456917cf41cf7f997094794a3 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Wed, 3 Nov 2021 10:38:53 +0530 Subject: [PATCH 3/9] used PlacesHistoryStorage for storage and readmeFix --- components/concept/storage/README.md | 2 +- components/feature/awesomebar/build.gradle | 2 + .../CombinedHistorySuggestionProviderTest.kt | 195 +---------------- .../HistoryStorageSuggestionProviderTest.kt | 197 +----------------- components/feature/toolbar/build.gradle | 2 + .../toolbar/ToolbarAutocompleteFeatureTest.kt | 190 +---------------- 6 files changed, 25 insertions(+), 563 deletions(-) diff --git a/components/concept/storage/README.md b/components/concept/storage/README.md index 994fb16c35c..7f691b4f94c 100644 --- a/components/concept/storage/README.md +++ b/components/concept/storage/README.md @@ -4,7 +4,7 @@ The `concept-storage` component contains interfaces and abstract classes that de This abstraction makes it possible to build components that work independently of the storage layer being used. -Currently two store implementations are available: +Currently a single store implementations is available: - [syncable, Rust Places storage](../../browser/storage-sync) - compatible with the Firefox Sync ecosystem ## Usage diff --git a/components/feature/awesomebar/build.gradle b/components/feature/awesomebar/build.gradle index 4a2cced89d0..506cd9a20f1 100644 --- a/components/feature/awesomebar/build.gradle +++ b/components/feature/awesomebar/build.gradle @@ -43,12 +43,14 @@ dependencies { testImplementation project(':support-test') testImplementation project(':lib-fetch-httpurlconnection') + testImplementation project(':browser-storage-sync') testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_mockwebserver + testImplementation Dependencies.mozilla_full_megazord_forUnitTests } apply from: '../../../publish.gradle' diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt index 3cb153cab43..e5f9b96aaba 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt @@ -6,25 +6,19 @@ package mozilla.components.feature.awesomebar.provider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking +import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.storage.DocumentType -import mozilla.components.concept.storage.FrecencyThresholdOption -import mozilla.components.concept.storage.HistoryAutocompleteResult import mozilla.components.concept.storage.HistoryMetadata import mozilla.components.concept.storage.HistoryMetadataKey import mozilla.components.concept.storage.HistoryMetadataStorage -import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.concept.storage.PageObservation import mozilla.components.concept.storage.PageVisit import mozilla.components.concept.storage.RedirectSource import mozilla.components.concept.storage.SearchResult -import mozilla.components.concept.storage.TopFrecentSiteInfo -import mozilla.components.concept.storage.VisitInfo import mozilla.components.concept.storage.VisitType import mozilla.components.support.test.eq import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.whenever -import mozilla.components.support.utils.StorageUtils -import mozilla.components.support.utils.segmentAwareDomainMatch import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test @@ -35,179 +29,6 @@ import org.mockito.Mockito.doReturn @RunWith(AndroidJUnit4::class) class CombinedHistorySuggestionProviderTest { - class TestingHistoryStorage : HistoryStorage { - - data class Visit(val timestamp: Long, val type: VisitType) - - val AUTOCOMPLETE_SOURCE_NAME = "memoryHistory" - - var pages: HashMap> = linkedMapOf() - val pageMeta: HashMap = hashMapOf() - - override suspend fun warmUp() { - // No-op for an in-memory store - } - - override suspend fun recordVisit(uri: String, visit: PageVisit) { - val now = System.currentTimeMillis() - if (visit.redirectSource != RedirectSource.NOT_A_SOURCE) { - return - } - - synchronized(pages) { - if (!pages.containsKey(uri)) { - pages[uri] = mutableListOf(Visit(now, visit.visitType)) - } else { - pages[uri]!!.add(Visit(now, visit.visitType)) - } - } - } - - override suspend fun recordObservation(uri: String, observation: PageObservation) = synchronized(pageMeta) { - val existingPageObservation = pageMeta[uri] - - if (existingPageObservation == null || - (!observation.title.isNullOrEmpty() && !observation.previewImageUrl.isNullOrEmpty()) - ) { - pageMeta[uri] = observation - } else if (!observation.title.isNullOrEmpty()) { - // Carryover the existing observed previewImageUrl - pageMeta[uri] = observation.copy(previewImageUrl = existingPageObservation.previewImageUrl) - } else { - // Carryover the existing observed title - pageMeta[uri] = observation.copy(title = existingPageObservation.title) - } - } - - override suspend fun getVisited(uris: List): List = synchronized(pages) { - return uris.map { - if (pages[it] != null && pages[it]!!.size > 0) { - return@map true - } - return@map false - } - } - - override suspend fun getVisited(): List = synchronized(pages) { - return pages.keys.toList() - } - - override suspend fun getVisitsPaginated(offset: Long, count: Long, excludeTypes: List): List { - throw UnsupportedOperationException("Pagination is not yet supported by the in-memory history storage") - } - - override suspend fun getDetailedVisits( - start: Long, - end: Long, - excludeTypes: List - ): List = synchronized(pages + pageMeta) { - val visits = mutableListOf() - - pages.forEach { - it.value.forEach { visit -> - if (visit.timestamp in start..end && !excludeTypes.contains(visit.type)) { - visits.add( - VisitInfo( - url = it.key, - title = pageMeta[it.key]?.title, - visitTime = visit.timestamp, - visitType = visit.type, - previewImageUrl = pageMeta[it.key]?.previewImageUrl - ) - ) - } - } - } - - return visits - } - - override suspend fun getTopFrecentSites( - numItems: Int, - frecencyThreshold: FrecencyThresholdOption - ): List { - throw UnsupportedOperationException("getTopFrecentSites is not yet supported by the in-memory history storage") - } - - override fun getSuggestions(query: String, limit: Int): List = synchronized(pages + pageMeta) { - data class Hit(val url: String, val score: Int) - - val urlMatches = pages.asSequence().map { - Hit(it.key, StorageUtils.levenshteinDistance(it.key, query)) - } - val titleMatches = pageMeta.asSequence().map { - Hit(it.key, StorageUtils.levenshteinDistance(it.value.title ?: "", query)) - } - val matchedUrls = mutableMapOf() - urlMatches.plus(titleMatches).forEach { - if (matchedUrls.containsKey(it.url) && matchedUrls[it.url]!! < it.score) { - matchedUrls[it.url] = it.score - } else { - matchedUrls[it.url] = it.score - } - } - // Calculate maxScore so that we can invert our scoring. - // Lower Levenshtein distance should produce a higher score. - val maxScore = urlMatches.maxByOrNull { it.score }?.score ?: return@synchronized listOf() - - matchedUrls.asSequence().sortedBy { it.value }.map { - SearchResult(id = it.key, score = maxScore - it.value, url = it.key, title = pageMeta[it.key]?.title) - }.take(limit).toList() - } - - override fun getAutocompleteSuggestion(query: String): HistoryAutocompleteResult? = synchronized(pages) { - return segmentAwareDomainMatch(query, pages.keys)?.let { urlMatch -> - HistoryAutocompleteResult( - query, urlMatch.matchedSegment, urlMatch.url, AUTOCOMPLETE_SOURCE_NAME, pages.size - ) - } - } - - override suspend fun deleteEverything() = synchronized(pages + pageMeta) { - pages.clear() - pageMeta.clear() - } - - override suspend fun deleteVisitsSince(since: Long) = synchronized(pages) { - pages.entries.forEach { - it.setValue(it.value.filterNot { visit -> visit.timestamp >= since }.toMutableList()) - } - pages = pages.filter { it.value.isNotEmpty() } as HashMap> - } - - override suspend fun deleteVisitsBetween(startTime: Long, endTime: Long) = synchronized(pages) { - pages.entries.forEach { - it.setValue( - it.value.filterNot { visit -> - visit.timestamp >= startTime && visit.timestamp <= endTime - }.toMutableList() - ) - } - pages = pages.filter { it.value.isNotEmpty() } as HashMap> - } - - override suspend fun deleteVisitsFor(url: String) = synchronized(pages + pageMeta) { - pages.remove(url) - pageMeta.remove(url) - Unit - } - - override suspend fun deleteVisit(url: String, timestamp: Long) = synchronized(pages) { - if (pages.containsKey(url)) { - pages[url] = pages[url]!!.filter { it.timestamp != timestamp }.toMutableList() - } - } - - // Not applicable. - override suspend fun prune() {} - - // Not applicable. - override suspend fun runMaintenance() {} - - // GC will take care of our internal data structures, so there's nothing to do here. - override fun cleanup() {} - } - private val historyEntry = HistoryMetadata( key = HistoryMetadataKey("http://www.mozilla.com", null, null), title = "mozilla", @@ -222,7 +43,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN history items exists WHEN onInputChanged is called with empty text THEN return empty suggestions list`() = runBlocking { val storage: HistoryMetadataStorage = mock() whenever(storage.queryHistoryMetadata("moz", DEFAULT_METADATA_SUGGESTION_LIMIT)).thenReturn(listOf(historyEntry)) - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) history.recordVisit("http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) val provider = CombinedHistorySuggestionProvider(history, storage, mock()) @@ -234,7 +55,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN more suggestions asked than metadata items exist WHEN user changes input THEN return a combined list of suggestions`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) history.recordVisit("http://www.mozilla.com/firefox", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) val provider = CombinedHistorySuggestionProvider(history, storage, mock()) @@ -249,7 +70,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN fewer suggestions asked than metadata items exist WHEN user changes input THEN return suggestions only based on metadata items`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) history.recordVisit("http://www.mozilla.com/firefox", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) val provider = CombinedHistorySuggestionProvider(history, storage, mock(), maxNumberOfSuggestions = 1) @@ -263,7 +84,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN only storage history items exist WHEN user changes input THEN return suggestions only based on storage items`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(emptyList()).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) history.recordVisit("http://www.mozilla.com/firefox", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) val provider = CombinedHistorySuggestionProvider(history, storage, mock(), maxNumberOfSuggestions = 1) @@ -277,7 +98,7 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN duplicated metadata and storage entries WHEN user changes input THEN return distinct suggestions`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) history.recordVisit( "http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE) @@ -327,7 +148,7 @@ class CombinedHistorySuggestionProviderTest { ) val metadataStorage: HistoryMetadataStorage = mock() - val historyStorage: TestingHistoryStorage = mock() + val historyStorage: PlacesHistoryStorage = mock() doReturn(listOf(metadataEntry2, metadataEntry1)).`when`(metadataStorage).queryHistoryMetadata(eq("moz"), anyInt()) doReturn(listOf(searchResult1, searchResult2)).`when`(historyStorage).getSuggestions(eq("moz"), anyInt()) diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt index ac4fb53566e..d30f7dc20ff 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt @@ -5,16 +5,12 @@ package mozilla.components.feature.awesomebar.provider import kotlinx.coroutines.runBlocking +import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.engine.Engine -import mozilla.components.concept.storage.FrecencyThresholdOption -import mozilla.components.concept.storage.HistoryAutocompleteResult import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.concept.storage.PageObservation import mozilla.components.concept.storage.PageVisit import mozilla.components.concept.storage.RedirectSource import mozilla.components.concept.storage.SearchResult -import mozilla.components.concept.storage.TopFrecentSiteInfo -import mozilla.components.concept.storage.VisitInfo import mozilla.components.concept.storage.VisitType import mozilla.components.feature.awesomebar.facts.AwesomeBarFacts import mozilla.components.support.base.Component @@ -24,8 +20,7 @@ import mozilla.components.support.base.facts.FactProcessor import mozilla.components.support.base.facts.Facts import mozilla.components.support.test.eq import mozilla.components.support.test.mock -import mozilla.components.support.utils.StorageUtils -import mozilla.components.support.utils.segmentAwareDomainMatch +import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -38,182 +33,6 @@ import org.mockito.Mockito.verify class HistoryStorageSuggestionProviderTest { - class TestingHistoryStorage : HistoryStorage { - - data class Visit(val timestamp: Long, val type: VisitType) - - val AUTOCOMPLETE_SOURCE_NAME = "memoryHistory" - - var pages: HashMap> = linkedMapOf() - val pageMeta: HashMap = hashMapOf() - - override suspend fun warmUp() { - // No-op for an in-memory store - } - - override suspend fun recordVisit(uri: String, visit: PageVisit) { - val now = System.currentTimeMillis() - if (visit.redirectSource != RedirectSource.NOT_A_SOURCE) { - return - } - - synchronized(pages) { - if (!pages.containsKey(uri)) { - pages[uri] = mutableListOf(Visit(now, visit.visitType)) - } else { - pages[uri]!!.add(Visit(now, visit.visitType)) - } - } - } - - override suspend fun recordObservation(uri: String, observation: PageObservation) = synchronized(pageMeta) { - val existingPageObservation = pageMeta[uri] - - if (existingPageObservation == null || - (!observation.title.isNullOrEmpty() && !observation.previewImageUrl.isNullOrEmpty()) - ) { - pageMeta[uri] = observation - } else if (!observation.title.isNullOrEmpty()) { - // Carryover the existing observed previewImageUrl - pageMeta[uri] = observation.copy(previewImageUrl = existingPageObservation.previewImageUrl) - } else { - // Carryover the existing observed title - pageMeta[uri] = observation.copy(title = existingPageObservation.title) - } - } - - override suspend fun getVisited(uris: List): List = synchronized(pages) { - return uris.map { - if (pages[it] != null && pages[it]!!.size > 0) { - return@map true - } - return@map false - } - } - - override suspend fun getVisited(): List = synchronized(pages) { - return pages.keys.toList() - } - - override suspend fun getVisitsPaginated(offset: Long, count: Long, excludeTypes: List): List { - throw UnsupportedOperationException("Pagination is not yet supported by the in-memory history storage") - } - - override suspend fun getDetailedVisits( - start: Long, - end: Long, - excludeTypes: List - ): List = synchronized(pages + pageMeta) { - val visits = mutableListOf() - - pages.forEach { - it.value.forEach { visit -> - if (visit.timestamp in start..end && !excludeTypes.contains(visit.type)) { - visits.add( - VisitInfo( - url = it.key, - title = pageMeta[it.key]?.title, - visitTime = visit.timestamp, - visitType = visit.type, - previewImageUrl = pageMeta[it.key]?.previewImageUrl - ) - ) - } - } - } - - return visits - } - - override suspend fun getTopFrecentSites( - numItems: Int, - frecencyThreshold: FrecencyThresholdOption - ): List { - throw UnsupportedOperationException("getTopFrecentSites is not yet supported by the in-memory history storage") - } - - override fun getSuggestions(query: String, limit: Int): List = synchronized(pages + pageMeta) { - data class Hit(val url: String, val score: Int) - - val urlMatches = pages.asSequence().map { - Hit(it.key, StorageUtils.levenshteinDistance(it.key, query)) - } - val titleMatches = pageMeta.asSequence().map { - Hit(it.key, StorageUtils.levenshteinDistance(it.value.title ?: "", query)) - } - val matchedUrls = mutableMapOf() - urlMatches.plus(titleMatches).forEach { - if (matchedUrls.containsKey(it.url) && matchedUrls[it.url]!! < it.score) { - matchedUrls[it.url] = it.score - } else { - matchedUrls[it.url] = it.score - } - } - // Calculate maxScore so that we can invert our scoring. - // Lower Levenshtein distance should produce a higher score. - val maxScore = urlMatches.maxByOrNull { it.score }?.score ?: return@synchronized listOf() - - matchedUrls.asSequence().sortedBy { it.value }.map { - SearchResult(id = it.key, score = maxScore - it.value, url = it.key, title = pageMeta[it.key]?.title) - }.take(limit).toList() - } - - override fun getAutocompleteSuggestion(query: String): HistoryAutocompleteResult? = synchronized(pages) { - return segmentAwareDomainMatch(query, pages.keys)?.let { urlMatch -> - HistoryAutocompleteResult( - query, urlMatch.matchedSegment, urlMatch.url, AUTOCOMPLETE_SOURCE_NAME, pages.size - ) - } - } - - override suspend fun deleteEverything() = synchronized(pages + pageMeta) { - pages.clear() - pageMeta.clear() - } - - override suspend fun deleteVisitsSince(since: Long) = synchronized(pages) { - pages.entries.forEach { - it.setValue(it.value.filterNot { visit -> visit.timestamp >= since }.toMutableList()) - } - pages = pages.filter { it.value.isNotEmpty() } as HashMap> - } - - override suspend fun deleteVisitsBetween(startTime: Long, endTime: Long) = synchronized(pages) { - pages.entries.forEach { - it.setValue( - it.value.filterNot { visit -> - visit.timestamp >= startTime && visit.timestamp <= endTime - }.toMutableList() - ) - } - pages = pages.filter { it.value.isNotEmpty() } as HashMap> - } - - override suspend fun deleteVisitsFor(url: String) = synchronized(pages + pageMeta) { - pages.remove(url) - pageMeta.remove(url) - Unit - } - - override suspend fun deleteVisit(url: String, timestamp: Long) = synchronized(pages) { - if (pages.containsKey(url)) { - pages[url] = pages[url]!!.filter { it.timestamp != timestamp }.toMutableList() - } - } - - override suspend fun prune() { - // Not applicable. - } - - override suspend fun runMaintenance() { - // Not applicable. - } - - override fun cleanup() { - // GC will take care of our internal data structures, so there's nothing to do here. - } - } - @Before fun setup() { Facts.clearProcessors() @@ -229,7 +48,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider returns suggestions from configured history storage`() = runBlocking { - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) val provider = HistoryStorageSuggestionProvider(history, mock()) history.recordVisit("http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) @@ -241,7 +60,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider limits number of returned suggestions to 20 by default`() = runBlocking { - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) val provider = HistoryStorageSuggestionProvider(history, mock()) for (i in 1..100) { @@ -254,7 +73,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider allows lowering the number of returned suggestions beneath the default`() = runBlocking { - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) val provider = HistoryStorageSuggestionProvider( historyStorage = history, loadUrlUseCase = mock(), maxNumberOfSuggestions = 2 ) @@ -269,7 +88,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider allows increasing the number of returned suggestions above the default`() = runBlocking { - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) val provider = HistoryStorageSuggestionProvider( historyStorage = history, loadUrlUseCase = mock(), maxNumberOfSuggestions = 22 ) @@ -323,7 +142,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `provider calls speculative connect for URL of highest scored suggestion`() = runBlocking { - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) val engine: Engine = mock() val provider = HistoryStorageSuggestionProvider(history, mock(), engine = engine) @@ -341,7 +160,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `fact is emitted when suggestion is clicked`() = runBlocking { - val history = TestingHistoryStorage() + val history = PlacesHistoryStorage(testContext) val engine: Engine = mock() val provider = HistoryStorageSuggestionProvider(history, mock(), engine = engine) diff --git a/components/feature/toolbar/build.gradle b/components/feature/toolbar/build.gradle index 2edb3b89e36..a4fb9752943 100644 --- a/components/feature/toolbar/build.gradle +++ b/components/feature/toolbar/build.gradle @@ -44,12 +44,14 @@ dependencies { implementation Dependencies.kotlin_coroutines testImplementation project(':support-test') + testImplementation project(':browser-storage-sync') testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_coroutines + testImplementation Dependencies.mozilla_full_megazord_forUnitTests } apply from: '../../../publish.gradle' diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt index e721aa370e3..6357546c9ed 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt @@ -9,16 +9,11 @@ import kotlinx.coroutines.runBlocking import mozilla.components.browser.domains.Domain import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.domains.autocomplete.DomainList +import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.engine.Engine -import mozilla.components.concept.storage.FrecencyThresholdOption -import mozilla.components.concept.storage.HistoryAutocompleteResult import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.concept.storage.PageObservation import mozilla.components.concept.storage.PageVisit import mozilla.components.concept.storage.RedirectSource -import mozilla.components.concept.storage.SearchResult -import mozilla.components.concept.storage.TopFrecentSiteInfo -import mozilla.components.concept.storage.VisitInfo import mozilla.components.concept.storage.VisitType import mozilla.components.concept.toolbar.AutocompleteDelegate import mozilla.components.concept.toolbar.AutocompleteResult @@ -27,8 +22,7 @@ import mozilla.components.support.test.any import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.eq import mozilla.components.support.test.mock -import mozilla.components.support.utils.StorageUtils.levenshteinDistance -import mozilla.components.support.utils.segmentAwareDomainMatch +import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertNotNull import org.junit.Assert.fail import org.junit.Test @@ -138,182 +132,6 @@ class ToolbarAutocompleteFeatureTest { } } - class TestingHistoryStorage : HistoryStorage { - - data class Visit(val timestamp: Long, val type: VisitType) - - val AUTOCOMPLETE_SOURCE_NAME = "memoryHistory" - - var pages: HashMap> = linkedMapOf() - val pageMeta: HashMap = hashMapOf() - - override suspend fun warmUp() { - // No-op for an in-memory store - } - - override suspend fun recordVisit(uri: String, visit: PageVisit) { - val now = System.currentTimeMillis() - if (visit.redirectSource != RedirectSource.NOT_A_SOURCE) { - return - } - - synchronized(pages) { - if (!pages.containsKey(uri)) { - pages[uri] = mutableListOf(Visit(now, visit.visitType)) - } else { - pages[uri]!!.add(Visit(now, visit.visitType)) - } - } - } - - override suspend fun recordObservation(uri: String, observation: PageObservation) = synchronized(pageMeta) { - val existingPageObservation = pageMeta[uri] - - if (existingPageObservation == null || - (!observation.title.isNullOrEmpty() && !observation.previewImageUrl.isNullOrEmpty()) - ) { - pageMeta[uri] = observation - } else if (!observation.title.isNullOrEmpty()) { - // Carryover the existing observed previewImageUrl - pageMeta[uri] = observation.copy(previewImageUrl = existingPageObservation.previewImageUrl) - } else { - // Carryover the existing observed title - pageMeta[uri] = observation.copy(title = existingPageObservation.title) - } - } - - override suspend fun getVisited(uris: List): List = synchronized(pages) { - return uris.map { - if (pages[it] != null && pages[it]!!.size > 0) { - return@map true - } - return@map false - } - } - - override suspend fun getVisited(): List = synchronized(pages) { - return pages.keys.toList() - } - - override suspend fun getVisitsPaginated(offset: Long, count: Long, excludeTypes: List): List { - throw UnsupportedOperationException("Pagination is not yet supported by the in-memory history storage") - } - - override suspend fun getDetailedVisits( - start: Long, - end: Long, - excludeTypes: List - ): List = synchronized(pages + pageMeta) { - val visits = mutableListOf() - - pages.forEach { - it.value.forEach { visit -> - if (visit.timestamp in start..end && !excludeTypes.contains(visit.type)) { - visits.add( - VisitInfo( - url = it.key, - title = pageMeta[it.key]?.title, - visitTime = visit.timestamp, - visitType = visit.type, - previewImageUrl = pageMeta[it.key]?.previewImageUrl - ) - ) - } - } - } - - return visits - } - - override suspend fun getTopFrecentSites( - numItems: Int, - frecencyThreshold: FrecencyThresholdOption - ): List { - throw UnsupportedOperationException("getTopFrecentSites is not yet supported by the in-memory history storage") - } - - override fun getSuggestions(query: String, limit: Int): List = synchronized(pages + pageMeta) { - data class Hit(val url: String, val score: Int) - - val urlMatches = pages.asSequence().map { - Hit(it.key, levenshteinDistance(it.key, query)) - } - val titleMatches = pageMeta.asSequence().map { - Hit(it.key, levenshteinDistance(it.value.title ?: "", query)) - } - val matchedUrls = mutableMapOf() - urlMatches.plus(titleMatches).forEach { - if (matchedUrls.containsKey(it.url) && matchedUrls[it.url]!! < it.score) { - matchedUrls[it.url] = it.score - } else { - matchedUrls[it.url] = it.score - } - } - // Calculate maxScore so that we can invert our scoring. - // Lower Levenshtein distance should produce a higher score. - val maxScore = urlMatches.maxByOrNull { it.score }?.score ?: return@synchronized listOf() - - matchedUrls.asSequence().sortedBy { it.value }.map { - SearchResult(id = it.key, score = maxScore - it.value, url = it.key, title = pageMeta[it.key]?.title) - }.take(limit).toList() - } - - override fun getAutocompleteSuggestion(query: String): HistoryAutocompleteResult? = synchronized(pages) { - return segmentAwareDomainMatch(query, pages.keys)?.let { urlMatch -> - HistoryAutocompleteResult( - query, urlMatch.matchedSegment, urlMatch.url, AUTOCOMPLETE_SOURCE_NAME, pages.size - ) - } - } - - override suspend fun deleteEverything() = synchronized(pages + pageMeta) { - pages.clear() - pageMeta.clear() - } - - override suspend fun deleteVisitsSince(since: Long) = synchronized(pages) { - pages.entries.forEach { - it.setValue(it.value.filterNot { visit -> visit.timestamp >= since }.toMutableList()) - } - pages = pages.filter { it.value.isNotEmpty() } as HashMap> - } - - override suspend fun deleteVisitsBetween(startTime: Long, endTime: Long) = synchronized(pages) { - pages.entries.forEach { - it.setValue( - it.value.filterNot { visit -> - visit.timestamp >= startTime && visit.timestamp <= endTime - }.toMutableList() - ) - } - pages = pages.filter { it.value.isNotEmpty() } as HashMap> - } - - override suspend fun deleteVisitsFor(url: String) = synchronized(pages + pageMeta) { - pages.remove(url) - pageMeta.remove(url) - Unit - } - - override suspend fun deleteVisit(url: String, timestamp: Long) = synchronized(pages) { - if (pages.containsKey(url)) { - pages[url] = pages[url]!!.filter { it.timestamp != timestamp }.toMutableList() - } - } - - override suspend fun prune() { - // Not applicable. - } - - override suspend fun runMaintenance() { - // Not applicable. - } - - override fun cleanup() { - // GC will take care of our internal data structures, so there's nothing to do here. - } - } - @Test fun `feature can be used without providers`() { val toolbar = TestToolbar() @@ -336,7 +154,7 @@ class ToolbarAutocompleteFeatureTest { var feature = ToolbarAutocompleteFeature(toolbar) val autocompleteDelegate: AutocompleteDelegate = mock() - var history: HistoryStorage = TestingHistoryStorage() + var history: HistoryStorage = PlacesHistoryStorage(testContext) val domains = object : BaseDomainAutocompleteProvider(DomainList.CUSTOM, { emptyList() }) { fun testDomains(list: List) { domains = list @@ -389,7 +207,7 @@ class ToolbarAutocompleteFeatureTest { ) // Can autocomplete with empty history and domain providers. - history = TestingHistoryStorage() + history = PlacesHistoryStorage(testContext) domains.testDomains(listOf()) feature.addHistoryStorageProvider(history) From a339d9fb1adf5c9214462b0292a02a801fe2c74e Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Wed, 3 Nov 2021 22:54:56 +0530 Subject: [PATCH 4/9] test fixes 1 --- components/feature/awesomebar/build.gradle | 2 ++ .../provider/HistoryStorageSuggestionProviderTest.kt | 3 +++ components/feature/toolbar/build.gradle | 2 ++ 3 files changed, 7 insertions(+) diff --git a/components/feature/awesomebar/build.gradle b/components/feature/awesomebar/build.gradle index 506cd9a20f1..a36a899f8b1 100644 --- a/components/feature/awesomebar/build.gradle +++ b/components/feature/awesomebar/build.gradle @@ -50,7 +50,9 @@ dependencies { testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_mockwebserver + testImplementation Dependencies.mozilla_full_megazord_forUnitTests + testImplementation Dependencies.mozilla_glean_forUnitTests } apply from: '../../../publish.gradle' diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt index d30f7dc20ff..e4566dc4d88 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt @@ -4,6 +4,7 @@ package mozilla.components.feature.awesomebar.provider +import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.engine.Engine @@ -25,12 +26,14 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyString import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +@RunWith(AndroidJUnit4::class) class HistoryStorageSuggestionProviderTest { @Before diff --git a/components/feature/toolbar/build.gradle b/components/feature/toolbar/build.gradle index a4fb9752943..805fcb706a1 100644 --- a/components/feature/toolbar/build.gradle +++ b/components/feature/toolbar/build.gradle @@ -51,7 +51,9 @@ dependencies { testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_coroutines + testImplementation Dependencies.mozilla_full_megazord_forUnitTests + testImplementation Dependencies.mozilla_glean_forUnitTests } apply from: '../../../publish.gradle' From 3af92afe2eded8a6d6bdfc0eb09bcadc2bd8f2a9 Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Thu, 4 Nov 2021 23:44:33 -0700 Subject: [PATCH 5/9] Update feature-awesomebar tests to use mocked HistoryStorage --- components/feature/awesomebar/build.gradle | 2 - .../HistoryStorageSuggestionProvider.kt | 1 + .../CombinedHistorySuggestionProviderTest.kt | 50 +++++++---------- .../HistoryStorageSuggestionProviderTest.kt | 56 +++++++++---------- 4 files changed, 47 insertions(+), 62 deletions(-) diff --git a/components/feature/awesomebar/build.gradle b/components/feature/awesomebar/build.gradle index a36a899f8b1..bea01800249 100644 --- a/components/feature/awesomebar/build.gradle +++ b/components/feature/awesomebar/build.gradle @@ -43,7 +43,6 @@ dependencies { testImplementation project(':support-test') testImplementation project(':lib-fetch-httpurlconnection') - testImplementation project(':browser-storage-sync') testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit @@ -51,7 +50,6 @@ dependencies { testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_mockwebserver - testImplementation Dependencies.mozilla_full_megazord_forUnitTests testImplementation Dependencies.mozilla_glean_forUnitTests } diff --git a/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProvider.kt b/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProvider.kt index ebceee44f30..d34a55b2fa3 100644 --- a/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProvider.kt +++ b/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProvider.kt @@ -55,6 +55,7 @@ class HistoryStorageSuggestionProvider( val suggestions = historyStorage.getSuggestions(text, maxNumberOfSuggestions) .sortedByDescending { it.score } .distinctBy { it.id } + .take(maxNumberOfSuggestions) suggestions.firstOrNull()?.url?.let { url -> engine?.speculativeConnect(url) } diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt index e5f9b96aaba..f9e4b7ce839 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt @@ -6,19 +6,14 @@ package mozilla.components.feature.awesomebar.provider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking -import mozilla.components.browser.storage.sync.PlacesHistoryStorage -import mozilla.components.concept.storage.DocumentType +import mozilla.components.concept.storage.HistoryStorage import mozilla.components.concept.storage.HistoryMetadata import mozilla.components.concept.storage.HistoryMetadataKey import mozilla.components.concept.storage.HistoryMetadataStorage -import mozilla.components.concept.storage.PageVisit -import mozilla.components.concept.storage.RedirectSource +import mozilla.components.concept.storage.DocumentType import mozilla.components.concept.storage.SearchResult -import mozilla.components.concept.storage.VisitType import mozilla.components.support.test.eq import mozilla.components.support.test.mock -import mozilla.components.support.test.robolectric.testContext -import mozilla.components.support.test.whenever import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test @@ -41,11 +36,11 @@ class CombinedHistorySuggestionProviderTest { @Test fun `GIVEN history items exists WHEN onInputChanged is called with empty text THEN return empty suggestions list`() = runBlocking { - val storage: HistoryMetadataStorage = mock() - whenever(storage.queryHistoryMetadata("moz", DEFAULT_METADATA_SUGGESTION_LIMIT)).thenReturn(listOf(historyEntry)) - val history = PlacesHistoryStorage(testContext) - history.recordVisit("http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - val provider = CombinedHistorySuggestionProvider(history, storage, mock()) + val metadata: HistoryMetadataStorage = mock() + doReturn(listOf(historyEntry)).`when`(metadata).queryHistoryMetadata(eq("moz"), anyInt()) + val history: HistoryStorage = mock() + doReturn(listOf(SearchResult("id", "http://www.mozilla.com", 10))).`when`(history).getSuggestions(eq("moz"), anyInt()) + val provider = CombinedHistorySuggestionProvider(history, metadata, mock()) assertTrue(provider.onInputChanged("").isEmpty()) assertTrue(provider.onInputChanged(" ").isEmpty()) @@ -55,23 +50,23 @@ class CombinedHistorySuggestionProviderTest { fun `GIVEN more suggestions asked than metadata items exist WHEN user changes input THEN return a combined list of suggestions`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = PlacesHistoryStorage(testContext) - history.recordVisit("http://www.mozilla.com/firefox", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) + val history: HistoryStorage = mock() + doReturn(listOf(SearchResult("id", "http://www.mozilla.com/firefox/", 10))).`when`(history).getSuggestions(eq("moz"), anyInt()) val provider = CombinedHistorySuggestionProvider(history, storage, mock()) val result = provider.onInputChanged("moz") assertEquals(2, result.size) assertEquals("http://www.mozilla.com", result[0].description) - assertEquals("http://www.mozilla.com/firefox", result[1].description) + assertEquals("http://www.mozilla.com/firefox/", result[1].description) } @Test fun `GIVEN fewer suggestions asked than metadata items exist WHEN user changes input THEN return suggestions only based on metadata items`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = PlacesHistoryStorage(testContext) - history.recordVisit("http://www.mozilla.com/firefox", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) + val history: HistoryStorage = mock() + doReturn(listOf(SearchResult("id", "http://www.mozilla.com/firefox/", 10))).`when`(history).getSuggestions(eq("moz"), anyInt()) val provider = CombinedHistorySuggestionProvider(history, storage, mock(), maxNumberOfSuggestions = 1) val result = provider.onInputChanged("moz") @@ -82,27 +77,24 @@ class CombinedHistorySuggestionProviderTest { @Test fun `GIVEN only storage history items exist WHEN user changes input THEN return suggestions only based on storage items`() = runBlocking { - val storage: HistoryMetadataStorage = mock() - doReturn(emptyList()).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = PlacesHistoryStorage(testContext) - history.recordVisit("http://www.mozilla.com/firefox", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - val provider = CombinedHistorySuggestionProvider(history, storage, mock(), maxNumberOfSuggestions = 1) + val metadata: HistoryMetadataStorage = mock() + doReturn(emptyList()).`when`(metadata).queryHistoryMetadata(eq("moz"), anyInt()) + val history: HistoryStorage = mock() + doReturn(listOf(SearchResult("id", "http://www.mozilla.com/firefox/", 10))).`when`(history).getSuggestions(eq("moz"), anyInt()) + val provider = CombinedHistorySuggestionProvider(history, metadata, mock(), maxNumberOfSuggestions = 1) val result = provider.onInputChanged("moz") assertEquals(1, result.size) - assertEquals("http://www.mozilla.com/firefox", result[0].description) + assertEquals("http://www.mozilla.com/firefox/", result[0].description) } @Test fun `GIVEN duplicated metadata and storage entries WHEN user changes input THEN return distinct suggestions`() = runBlocking { val storage: HistoryMetadataStorage = mock() doReturn(listOf(historyEntry)).`when`(storage).queryHistoryMetadata(eq("moz"), anyInt()) - val history = PlacesHistoryStorage(testContext) - history.recordVisit( - "http://www.mozilla.com", - PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE) - ) + val history: HistoryStorage = mock() + doReturn(listOf(SearchResult("id", "http://www.mozilla.com", 10))).`when`(history).getSuggestions(eq("moz"), anyInt()) val provider = CombinedHistorySuggestionProvider(history, storage, mock()) val result = provider.onInputChanged("moz") @@ -148,7 +140,7 @@ class CombinedHistorySuggestionProviderTest { ) val metadataStorage: HistoryMetadataStorage = mock() - val historyStorage: PlacesHistoryStorage = mock() + val historyStorage: HistoryStorage = mock() doReturn(listOf(metadataEntry2, metadataEntry1)).`when`(metadataStorage).queryHistoryMetadata(eq("moz"), anyInt()) doReturn(listOf(searchResult1, searchResult2)).`when`(historyStorage).getSuggestions(eq("moz"), anyInt()) diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt index e4566dc4d88..4b354940c2c 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt @@ -6,13 +6,9 @@ package mozilla.components.feature.awesomebar.provider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking -import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.concept.storage.PageVisit -import mozilla.components.concept.storage.RedirectSource import mozilla.components.concept.storage.SearchResult -import mozilla.components.concept.storage.VisitType import mozilla.components.feature.awesomebar.facts.AwesomeBarFacts import mozilla.components.support.base.Component import mozilla.components.support.base.facts.Action @@ -21,13 +17,13 @@ import mozilla.components.support.base.facts.FactProcessor import mozilla.components.support.base.facts.Facts import mozilla.components.support.test.eq import mozilla.components.support.test.mock -import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -51,55 +47,53 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider returns suggestions from configured history storage`() = runBlocking { - val history = PlacesHistoryStorage(testContext) + val history: HistoryStorage = mock() + Mockito.doReturn(listOf(SearchResult("id", "http://www.mozilla.com/", 10))).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) val provider = HistoryStorageSuggestionProvider(history, mock()) - history.recordVisit("http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - val suggestions = provider.onInputChanged("moz") assertEquals(1, suggestions.size) - assertEquals("http://www.mozilla.com", suggestions[0].description) + assertEquals("http://www.mozilla.com/", suggestions[0].description) } @Test - fun `Provider limits number of returned suggestions to 20 by default`() = runBlocking { - val history = PlacesHistoryStorage(testContext) - val provider = HistoryStorageSuggestionProvider(history, mock()) - - for (i in 1..100) { - history.recordVisit("http://www.mozilla.com/$i", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - } + fun `Provider limits number of returned suggestions to a max of 20 by default`() = runBlocking { + val history: HistoryStorage = mock() + Mockito.doReturn((1..100).map { + SearchResult("id$it", "http://www.mozilla.com/$it/", 10) + }).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) + val provider = HistoryStorageSuggestionProvider(history, mock()) val suggestions = provider.onInputChanged("moz") assertEquals(20, suggestions.size) } @Test fun `Provider allows lowering the number of returned suggestions beneath the default`() = runBlocking { - val history = PlacesHistoryStorage(testContext) + val history: HistoryStorage = mock() + Mockito.doReturn((1..50).map { + SearchResult("id$it", "http://www.mozilla.com/$it/", 10) + }).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) + val provider = HistoryStorageSuggestionProvider( historyStorage = history, loadUrlUseCase = mock(), maxNumberOfSuggestions = 2 ) - for (i in 1..50) { - history.recordVisit("http://www.mozilla.com/$i", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - } - val suggestions = provider.onInputChanged("moz") assertEquals(2, suggestions.size) } @Test fun `Provider allows increasing the number of returned suggestions above the default`() = runBlocking { - val history = PlacesHistoryStorage(testContext) + val history: HistoryStorage = mock() + Mockito.doReturn((1..50).map { + SearchResult("id$it", "http://www.mozilla.com/$it/", 10) + }).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) + val provider = HistoryStorageSuggestionProvider( historyStorage = history, loadUrlUseCase = mock(), maxNumberOfSuggestions = 22 ) - for (i in 1..50) { - history.recordVisit("http://www.mozilla.com/$i", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - } - val suggestions = provider.onInputChanged("moz") assertEquals(22, suggestions.size) } @@ -145,7 +139,7 @@ class HistoryStorageSuggestionProviderTest { @Test fun `provider calls speculative connect for URL of highest scored suggestion`() = runBlocking { - val history = PlacesHistoryStorage(testContext) + val history: HistoryStorage = mock() val engine: Engine = mock() val provider = HistoryStorageSuggestionProvider(history, mock(), engine = engine) @@ -153,17 +147,17 @@ class HistoryStorageSuggestionProviderTest { assertTrue(suggestions.isEmpty()) verify(engine, never()).speculativeConnect(anyString()) - history.recordVisit("http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) + Mockito.doReturn(listOf(SearchResult("id", "http://www.mozilla.com/", 10))).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) suggestions = provider.onInputChanged("moz") assertEquals(1, suggestions.size) - assertEquals("http://www.mozilla.com", suggestions[0].description) + assertEquals("http://www.mozilla.com/", suggestions[0].description) verify(engine, times(1)).speculativeConnect(suggestions[0].description!!) } @Test fun `fact is emitted when suggestion is clicked`() = runBlocking { - val history = PlacesHistoryStorage(testContext) + val history: HistoryStorage = mock() val engine: Engine = mock() val provider = HistoryStorageSuggestionProvider(history, mock(), engine = engine) @@ -171,7 +165,7 @@ class HistoryStorageSuggestionProviderTest { assertTrue(suggestions.isEmpty()) verify(engine, never()).speculativeConnect(anyString()) - history.recordVisit("http://www.mozilla.com", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) + Mockito.doReturn(listOf(SearchResult("id", "http://www.mozilla.com/", 10))).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) suggestions = provider.onInputChanged("moz") assertEquals(1, suggestions.size) From 00930027570689f58c48abb8e4d73d960660c929 Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Thu, 4 Nov 2021 23:58:46 -0700 Subject: [PATCH 6/9] Linter fixes for feature-awesomebar --- .../CombinedHistorySuggestionProviderTest.kt | 4 ++-- .../HistoryStorageSuggestionProviderTest.kt | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt index f9e4b7ce839..ceb7c2081cd 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/CombinedHistorySuggestionProviderTest.kt @@ -6,11 +6,11 @@ package mozilla.components.feature.awesomebar.provider import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.runBlocking -import mozilla.components.concept.storage.HistoryStorage +import mozilla.components.concept.storage.DocumentType import mozilla.components.concept.storage.HistoryMetadata import mozilla.components.concept.storage.HistoryMetadataKey import mozilla.components.concept.storage.HistoryMetadataStorage -import mozilla.components.concept.storage.DocumentType +import mozilla.components.concept.storage.HistoryStorage import mozilla.components.concept.storage.SearchResult import mozilla.components.support.test.eq import mozilla.components.support.test.mock diff --git a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt index 4b354940c2c..a50f4c46c22 100644 --- a/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt +++ b/components/feature/awesomebar/src/test/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProviderTest.kt @@ -59,9 +59,11 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider limits number of returned suggestions to a max of 20 by default`() = runBlocking { val history: HistoryStorage = mock() - Mockito.doReturn((1..100).map { - SearchResult("id$it", "http://www.mozilla.com/$it/", 10) - }).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) + Mockito.doReturn( + (1..100).map { + SearchResult("id$it", "http://www.mozilla.com/$it/", 10) + } + ).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) val provider = HistoryStorageSuggestionProvider(history, mock()) val suggestions = provider.onInputChanged("moz") @@ -71,9 +73,11 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider allows lowering the number of returned suggestions beneath the default`() = runBlocking { val history: HistoryStorage = mock() - Mockito.doReturn((1..50).map { - SearchResult("id$it", "http://www.mozilla.com/$it/", 10) - }).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) + Mockito.doReturn( + (1..50).map { + SearchResult("id$it", "http://www.mozilla.com/$it/", 10) + } + ).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) val provider = HistoryStorageSuggestionProvider( historyStorage = history, loadUrlUseCase = mock(), maxNumberOfSuggestions = 2 @@ -86,9 +90,11 @@ class HistoryStorageSuggestionProviderTest { @Test fun `Provider allows increasing the number of returned suggestions above the default`() = runBlocking { val history: HistoryStorage = mock() - Mockito.doReturn((1..50).map { - SearchResult("id$it", "http://www.mozilla.com/$it/", 10) - }).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) + Mockito.doReturn( + (1..50).map { + SearchResult("id$it", "http://www.mozilla.com/$it/", 10) + } + ).`when`(history).getSuggestions(eq("moz"), Mockito.anyInt()) val provider = HistoryStorageSuggestionProvider( historyStorage = history, loadUrlUseCase = mock(), maxNumberOfSuggestions = 22 From e44cd5b1350e033dfa8015c79a3fb88482214eb1 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Fri, 5 Nov 2021 12:40:25 +0530 Subject: [PATCH 7/9] toolbar fix 1 --- components/feature/toolbar/build.gradle | 2 -- .../feature/toolbar/ToolbarAutocompleteFeatureTest.kt | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/components/feature/toolbar/build.gradle b/components/feature/toolbar/build.gradle index 805fcb706a1..6cc9647aaf1 100644 --- a/components/feature/toolbar/build.gradle +++ b/components/feature/toolbar/build.gradle @@ -44,7 +44,6 @@ dependencies { implementation Dependencies.kotlin_coroutines testImplementation project(':support-test') - testImplementation project(':browser-storage-sync') testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit @@ -52,7 +51,6 @@ dependencies { testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_coroutines - testImplementation Dependencies.mozilla_full_megazord_forUnitTests testImplementation Dependencies.mozilla_glean_forUnitTests } diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt index 6357546c9ed..66de698f2c4 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.runBlocking import mozilla.components.browser.domains.Domain import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.domains.autocomplete.DomainList -import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.HistoryStorage import mozilla.components.concept.storage.PageVisit @@ -22,7 +21,6 @@ import mozilla.components.support.test.any import mozilla.components.support.test.argumentCaptor import mozilla.components.support.test.eq import mozilla.components.support.test.mock -import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertNotNull import org.junit.Assert.fail import org.junit.Test @@ -154,7 +152,7 @@ class ToolbarAutocompleteFeatureTest { var feature = ToolbarAutocompleteFeature(toolbar) val autocompleteDelegate: AutocompleteDelegate = mock() - var history: HistoryStorage = PlacesHistoryStorage(testContext) + var history: HistoryStorage = mock() val domains = object : BaseDomainAutocompleteProvider(DomainList.CUSTOM, { emptyList() }) { fun testDomains(list: List) { domains = list @@ -207,7 +205,7 @@ class ToolbarAutocompleteFeatureTest { ) // Can autocomplete with empty history and domain providers. - history = PlacesHistoryStorage(testContext) + history = mock() domains.testDomains(listOf()) feature.addHistoryStorageProvider(history) From 446e57daf94c454d969f91b846514bfac7f79557 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Fri, 5 Nov 2021 13:48:17 +0530 Subject: [PATCH 8/9] toolbar fix --- .../toolbar/ToolbarAutocompleteFeatureTest.kt | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt index 66de698f2c4..755b5db0a10 100644 --- a/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt +++ b/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/ToolbarAutocompleteFeatureTest.kt @@ -10,10 +10,8 @@ import mozilla.components.browser.domains.Domain import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider import mozilla.components.browser.domains.autocomplete.DomainList import mozilla.components.concept.engine.Engine +import mozilla.components.concept.storage.HistoryAutocompleteResult import mozilla.components.concept.storage.HistoryStorage -import mozilla.components.concept.storage.PageVisit -import mozilla.components.concept.storage.RedirectSource -import mozilla.components.concept.storage.VisitType import mozilla.components.concept.toolbar.AutocompleteDelegate import mozilla.components.concept.toolbar.AutocompleteResult import mozilla.components.concept.toolbar.Toolbar @@ -25,6 +23,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.fail import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.doReturn import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.times @@ -164,9 +163,15 @@ class ToolbarAutocompleteFeatureTest { verifyNoAutocompleteResult(toolbar, autocompleteDelegate, "hi") // Can autocomplete with a non-empty history provider. - runBlocking { - history.recordVisit("https://www.mozilla.org", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - } + doReturn( + HistoryAutocompleteResult( + input = "mo", + text = "mozilla.org", + url = "https://www.mozilla.org", + source = "memoryHistory", + totalItems = 1 + ) + ).`when`(history).getAutocompleteSuggestion("mo") verifyNoAutocompleteResult(toolbar, autocompleteDelegate, "hi") verifyAutocompleteResult( @@ -231,9 +236,15 @@ class ToolbarAutocompleteFeatureTest { ) ) - runBlocking { - history.recordVisit("https://www.mozilla.org", PageVisit(VisitType.TYPED, RedirectSource.NOT_A_SOURCE)) - } + doReturn( + HistoryAutocompleteResult( + input = "mo", + text = "mozilla.org", + url = "https://www.mozilla.org", + source = "memoryHistory", + totalItems = 1 + ) + ).`when`(history).getAutocompleteSuggestion("mo") verifyAutocompleteResult( toolbar, autocompleteDelegate, "mo", From 437c847f061b83c06fe5984cb78b1c7f83b39461 Mon Sep 17 00:00:00 2001 From: 4shutosh <4shutoshsingh@gmail.com> Date: Fri, 5 Nov 2021 14:28:10 +0530 Subject: [PATCH 9/9] final code cleanup --- components/concept/storage/README.md | 2 +- components/feature/awesomebar/build.gradle | 2 -- components/feature/toolbar/build.gradle | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/components/concept/storage/README.md b/components/concept/storage/README.md index 7f691b4f94c..5afcfb9e293 100644 --- a/components/concept/storage/README.md +++ b/components/concept/storage/README.md @@ -4,7 +4,7 @@ The `concept-storage` component contains interfaces and abstract classes that de This abstraction makes it possible to build components that work independently of the storage layer being used. -Currently a single store implementations is available: +Currently a single store implementation is available: - [syncable, Rust Places storage](../../browser/storage-sync) - compatible with the Firefox Sync ecosystem ## Usage diff --git a/components/feature/awesomebar/build.gradle b/components/feature/awesomebar/build.gradle index bea01800249..4a2cced89d0 100644 --- a/components/feature/awesomebar/build.gradle +++ b/components/feature/awesomebar/build.gradle @@ -49,8 +49,6 @@ dependencies { testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_mockwebserver - - testImplementation Dependencies.mozilla_glean_forUnitTests } apply from: '../../../publish.gradle' diff --git a/components/feature/toolbar/build.gradle b/components/feature/toolbar/build.gradle index 6cc9647aaf1..2edb3b89e36 100644 --- a/components/feature/toolbar/build.gradle +++ b/components/feature/toolbar/build.gradle @@ -50,8 +50,6 @@ dependencies { testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_robolectric testImplementation Dependencies.testing_coroutines - - testImplementation Dependencies.mozilla_glean_forUnitTests } apply from: '../../../publish.gradle'