Skip to content

Commit

Permalink
add ShowOnAppLaunchUrlConverter
Browse files Browse the repository at this point in the history
This class will take the url that a user has entered and if:

1. It is blank will set it as a default to duckduckgo.com
2. If it has no scheme will add http (in case the website does not currently support https)
3. Other wise return what was passed in
  • Loading branch information
mikescamell committed Sep 19, 2024
1 parent b40783d commit 8949392
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.generalsettings.showonapplaunch

import android.net.Uri
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore

class ShowOnAppLaunchUrlConverterImpl : UrlConverter {

override fun convertUrl(url: String?): String {
if (url.isNullOrBlank()) return ShowOnAppLaunchOptionDataStore.DEFAULT_SPECIFIC_PAGE_URL

val uri = Uri.parse(url.trim())

val convertedUri = if (uri.scheme == null) {
Uri.Builder().scheme("http").authority(uri.path?.lowercase())
} else {
uri.buildUpon()
.scheme(uri.scheme?.lowercase())
.authority(uri.authority?.lowercase())
}
.apply {
query(uri.query)
fragment(uri.fragment)
}
.build()
.toString()

return Uri.decode(convertedUri)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import timber.log.Timber
class ShowOnAppLaunchViewModel @Inject constructor(
private val dispatcherProvider: DispatcherProvider,
private val showOnAppLaunchOptionDataStore: ShowOnAppLaunchOptionDataStore,
private val urlConverter: UrlConverter,
) : ViewModel() {

data class ViewState(
Expand Down Expand Up @@ -71,7 +72,8 @@ class ShowOnAppLaunchViewModel @Inject constructor(
fun setSpecificPageUrl(url: String) {
Timber.i("Setting specific page url to $url")
viewModelScope.launch(dispatcherProvider.io()) {
showOnAppLaunchOptionDataStore.setSpecificPageUrl(url)
val convertedUrl = urlConverter.convertUrl(url)
showOnAppLaunchOptionDataStore.setSpecificPageUrl(convertedUrl)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.generalsettings.showonapplaunch

interface UrlConverter {

fun convertUrl(url: String?): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import com.duckduckgo.app.generalsettings.showonapplaunch.ShowOnAppLaunchUrlConverterImpl
import com.duckduckgo.app.generalsettings.showonapplaunch.UrlConverter
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
Expand All @@ -37,6 +39,9 @@ object ShowOnAppLaunchDataStoreModule {
@Provides
@ShowOnAppLaunch
fun showOnAppLaunchDataStore(context: Context): DataStore<Preferences> = context.showOnAppLaunchDataStore

@Provides
fun showOnAppLaunchUrlConverter(): UrlConverter = ShowOnAppLaunchUrlConverterImpl()
}

@Qualifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchO
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.LastOpenedTab
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.NewTabPage
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.SpecificPage
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore.Companion.DEFAULT_SPECIFIC_PAGE_URL
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import javax.inject.Inject
Expand All @@ -37,6 +38,10 @@ interface ShowOnAppLaunchOptionDataStore {

suspend fun setShowOnAppLaunchOption(showOnAppLaunchOption: ShowOnAppLaunchOption)
suspend fun setSpecificPageUrl(url: String)

companion object {
const val DEFAULT_SPECIFIC_PAGE_URL = "https://duckduckgo.com"
}
}

@ContributesBinding(AppScope::class)
Expand Down Expand Up @@ -79,7 +84,6 @@ class ShowOnAppLaunchOptionPrefsDataStore @Inject constructor(
}

companion object {
private const val DEFAULT_SPECIFIC_PAGE_URL = "duckduckgo.com"
private const val KEY_SHOW_ON_APP_LAUNCH_OPTION = "SHOW_ON_APP_LAUNCH_OPTION"
private const val KEY_SHOW_ON_APP_LAUNCH_SPECIFIC_PAGE_URL = "SHOW_ON_APP_LAUNCH_SPECIFIC_PAGE_URL"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.generalsettings.showonapplaunch

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore.Companion.DEFAULT_SPECIFIC_PAGE_URL
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ShowOnAppLaunchUrlConverterImplTest {

private val urlConverter = ShowOnAppLaunchUrlConverterImpl()

@Test
fun whenUrlIsNullThenShouldReturnDefaultUrl() {
val result = urlConverter.convertUrl(null)
assertEquals(DEFAULT_SPECIFIC_PAGE_URL, result)
}

@Test
fun whenUrlIsEmptyThenShouldReturnDefaultUrl() {
val result = urlConverter.convertUrl("")
assertEquals(DEFAULT_SPECIFIC_PAGE_URL, result)
}

@Test
fun whenUrlIsBlankThenShouldReturnDefaultUrl() {
val result = urlConverter.convertUrl(" ")
assertEquals(DEFAULT_SPECIFIC_PAGE_URL, result)
}

@Test
fun whenUrlHasNoSchemeThenHttpSchemeIsAdded() {
val result = urlConverter.convertUrl("www.example.com")
assertEquals("http://www.example.com", result)
}

@Test
fun whenUrlHasNoSchemeAndSubdomainThenHttpSchemeIsAdded() {
val result = urlConverter.convertUrl("example.com")
assertEquals("http://example.com", result)
}

@Test
fun whenUrlHasASchemeThenShouldReturnTheSameUrl() {
val result = urlConverter.convertUrl("https://www.example.com")
assertEquals("https://www.example.com", result)
}

@Test
fun whenUrlHasDifferentSchemeThenShouldReturnTheSameUrl() {
val result = urlConverter.convertUrl("ftp://www.example.com")
assertEquals("ftp://www.example.com", result)
}

@Test
fun whenUrlHasSpecialCharactersThenShouldReturnTheSameUrl() {
val result = urlConverter.convertUrl("https://www.example.com/path?query=param&another=param")
assertEquals("https://www.example.com/path?query=param&another=param", result)
}

@Test
fun whenUrlHasPortThenShouldReturnTheSameUrl() {
val result = urlConverter.convertUrl("https://www.example.com:8080")
assertEquals("https://www.example.com:8080", result)
}

@Test
fun whenUrlHasPathAndQueryParametersThenShouldReturnTheSameUrl() {
val result = urlConverter.convertUrl("https://www.example.com/path/to/resource?query=param")
assertEquals("https://www.example.com/path/to/resource?query=param", result)
}

@Test
fun whenUrlHasUppercaseProtocolThenShouldLowercaseProtocol() {
val result = urlConverter.convertUrl("HTTPS://www.example.com")
assertEquals("https://www.example.com", result)
}

@Test
fun whenUrlHasUppercaseSubdomainThenShouldLowercaseSubdomain() {
val result = urlConverter.convertUrl("https://WWW.example.com")
assertEquals("https://www.example.com", result)
}

@Test
fun whenUrlHasUppercaseDomainThenShouldLowercaseDomain() {
val result = urlConverter.convertUrl("https://www.EXAMPLE.com")
assertEquals("https://www.example.com", result)
}

@Test
fun whenUrlHasUppercaseTopLevelDomainThenShouldLowercaseTopLevelDomain() {
val result = urlConverter.convertUrl("https://www.example.COM")
assertEquals("https://www.example.com", result)
}

@Test
fun whenUrlHasMixedCaseThenOnlyProtocolSubdomainDomainAndTldAreLowercased() {
val result = urlConverter.convertUrl("HTTPS://WWW.EXAMPLE.COM/Path?Query=Param#Fragment")
assertEquals("https://www.example.com/Path?Query=Param#Fragment", result)
}

@Test
fun whenUrlIsNotAValidUrlReturnsInvalidUrlWithHttpScheme() {
val result = urlConverter.convertUrl("example")
assertEquals("http://example", result)
}
}

0 comments on commit 8949392

Please sign in to comment.