Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/SalesforceSDK/res/values/sf__strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<string name="sf__more_options">More Options</string>
<string name="sf__pick_server">Change Server</string>
<string name="sf__clear_cookies">Clear Cookies</string>
<string name="sf__clear_caches">Clear Caches</string>
<string name="sf__reload">Reload</string>
<string name="sf__launch_idp">Log In with IDP App</string>
<string name="sf__login_with_biometric">Log In with Biometric</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ open class LoginViewModel(val bootConfig: BootConfig) : ViewModel() {
open fun clearCookies() =
CookieManager.getInstance().removeAllCookies(null)

/** Clear WebView Caches. */
open fun clearCaches(webView: android.webkit.WebView) {
webView.clearCache(true)
}

@brandonpage brandonpage Aug 29, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to also reload after clearing cache? I believe we do that when we clear cookies.

Should be noted that reloading will cycle the web server code verifier but I don't think that will matter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller does the reload, see https://github.com/forcedotcom/SalesforceMobileSDK-Android/pull/2752/files#diff-321c03965ac0663f16817520073fbe2d6def698c5568ab388f1161c35536d069R303

NB: it's the same with clearCookies.

    open fun clearCookies() =
        CookieManager.getInstance().removeAllCookies(null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, I missed that. Great!


/**
* Automatically log in using the provided UI Bridge API parameters.
* @param frontdoorBridgeUrl The UI Bridge API front door bridge API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.viewmodel.compose.viewModel
import com.salesforce.androidsdk.R.string.sf__back_button_content_description
import com.salesforce.androidsdk.R.string.sf__clear_cookies
import com.salesforce.androidsdk.R.string.sf__clear_caches
import com.salesforce.androidsdk.R.string.sf__launch_idp
import com.salesforce.androidsdk.R.string.sf__loading_indicator
import com.salesforce.androidsdk.R.string.sf__more_options
Expand Down Expand Up @@ -149,6 +150,7 @@ fun LoginView() {
titleTextColor = viewModel.titleTextColor ?: viewModel.dynamicHeaderTextColor.value,
showServerPicker = viewModel.showServerPicker,
clearCookies = { viewModel.clearCookies() },
clearCaches = { viewModel.clearCaches(activity.webView) },
reloadWebView = { viewModel.reloadWebView() },
shouldShowBackButton = viewModel.shouldShowBackButton,
finish = { activity.handleBackBehavior() },
Expand Down Expand Up @@ -246,6 +248,7 @@ internal fun DefaultTopAppBar(
titleTextColor: Color,
showServerPicker: MutableState<Boolean>,
clearCookies: () -> Unit,
clearCaches: () -> Unit,
reloadWebView: () -> Unit,
shouldShowBackButton: Boolean,
finish: () -> Unit,
Expand Down Expand Up @@ -295,6 +298,11 @@ internal fun DefaultTopAppBar(
reloadWebView()
showMenu = false
}
MenuItem(stringResource(sf__clear_caches)) {
clearCaches()
reloadWebView()
showMenu = false
}
MenuItem(stringResource(sf__reload)) {
reloadWebView()
showMenu = false
Expand Down Expand Up @@ -474,6 +482,7 @@ private fun AppBarPreview() {
titleTextColor = Color.Black,
showServerPicker = remember { mutableStateOf(false) },
clearCookies = { },
clearCaches = { },
reloadWebView = { },
shouldShowBackButton = false,
finish = { },
Expand All @@ -493,6 +502,7 @@ private fun AppBarLoadingPreview() {
titleTextColor = Color.Black,
showServerPicker = remember { mutableStateOf(false) },
clearCookies = { },
clearCaches = { },
reloadWebView = { },
shouldShowBackButton = false,
finish = { },
Expand All @@ -512,6 +522,7 @@ private fun AppBarBackButtonPreview() {
titleTextColor = Color.Black,
showServerPicker = remember { mutableStateOf(false) },
clearCookies = { },
clearCaches = { },
reloadWebView = { },
shouldShowBackButton = true,
finish = { },
Expand All @@ -531,6 +542,7 @@ private fun AppBarDarkPreview() {
titleTextColor = Color.White,
showServerPicker = remember { mutableStateOf(false) },
clearCookies = { },
clearCaches = { },
reloadWebView = { },
shouldShowBackButton = true,
finish = { },
Expand All @@ -550,6 +562,7 @@ private fun BlueAppBarPreview() {
titleTextColor = Color.White,
showServerPicker = remember { mutableStateOf(false) },
clearCookies = { },
clearCaches = { },
reloadWebView = { },
shouldShowBackButton = true,
finish = { },
Expand All @@ -569,6 +582,7 @@ private fun BlueAppBarLoadingPreview() {
titleTextColor = Color.White,
showServerPicker = remember { mutableStateOf(false) },
clearCookies = { },
clearCaches = { },
reloadWebView = { },
shouldShowBackButton = true,
finish = { },
Expand All @@ -587,6 +601,7 @@ private fun CustomTextAppBarPreview() {
titleTextColor = Color.White,
showServerPicker = remember { mutableStateOf(false) },
clearCookies = { },
clearCaches = { },
reloadWebView = { },
shouldShowBackButton = false,
finish = { },
Expand All @@ -605,6 +620,7 @@ private fun CustomTextAppBarLoadingPreview() {
titleTextColor = Color.White,
showServerPicker = remember { mutableStateOf(false) },
clearCookies = { },
clearCaches = { },
reloadWebView = { },
shouldShowBackButton = false,
finish = { },
Expand All @@ -623,6 +639,7 @@ private fun LongCustomTextAppBarPreview() {
titleTextColor = Color.Black,
showServerPicker = remember { mutableStateOf(false) },
clearCookies = { },
clearCaches = { },
reloadWebView = { },
shouldShowBackButton = true,
finish = { },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@
*/
package com.salesforce.androidsdk.auth

import android.webkit.WebView
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import com.salesforce.androidsdk.R.string.oauth_display_type
import com.salesforce.androidsdk.app.SalesforceSDKManager
import com.salesforce.androidsdk.auth.OAuth2.getFrontdoorUrl
Expand Down Expand Up @@ -197,6 +200,18 @@
assertEquals(unchangedUrl, viewModel.getValidServerUrl(endingSlash))
}

@Test
fun clearCaches_CallsWebViewClearCache_WithTrueParameter() {
// Arrange
val mockWebView = mock(WebView::class.java)

Check failure on line 206 in libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/LoginViewModelTest.kt

View workflow job for this annotation

GitHub Actions / SalesforceSDK Test Results | api_34_test_result.xml

LoginViewModelTest.clearCaches_CallsWebViewClearCache_WithTrueParameter

org.mockito.exceptions.base.MockitoException: Mockito cannot mock this class: class android.webkit.WebView.
Raw output
org.mockito.exceptions.base.MockitoException: 
Mockito cannot mock this class: class android.webkit.WebView.

Mockito can only mock non-private & non-final classes.
If you're not sure why you're getting this error, please report to the mailing list.



IMPORTANT INFORMATION FOR ANDROID USERS:

The regular Byte Buddy mock makers cannot generate code on an Android VM!
To resolve this, please use the 'mockito-android' dependency for your application:
http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22mockito-android%22%20g%3A%22org.mockito%22

Java               : 0.9
JVM vendor name    : The Android Project
JVM vendor version : 2.1.0
JVM name           : Dalvik
JVM version        : 0.9
JVM info           : null
OS name            : Linux
OS version         : 6.1.23-android14-4-00257-g7e35917775b8-ab9964412


Underlying exception : java.lang.IllegalStateException: java.lang.UnsupportedOperationException: can't load this type of class file
	at com.salesforce.androidsdk.auth.LoginViewModelTest.clearCaches_CallsWebViewClearCache_WithTrueParameter(LoginViewModelTest.kt:206)
	... 35 trimmed
Caused by: java.lang.IllegalStateException: java.lang.UnsupportedOperationException: can't load this type of class file
	at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Direct.defineClass(ClassInjector.java:689)
	at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection.injectRaw(ClassInjector.java:284)
	at net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase.inject(ClassInjector.java:118)
	at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$InjectionDispatcher.load(ClassLoadingStrategy.java:241)
	at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:101)
	at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6325)
	at org.mockito.internal.creation.bytebuddy.SubclassBytecodeGenerator.mockClass(SubclassBytecodeGenerator.java:173)
	at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$1.call(TypeCachingBytecodeGenerator.java:37)
	at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$1.call(TypeCachingBytecodeGenerator.java:34)
	at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:168)
	at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:399)
	at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:190)
	at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:410)
	at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClass(TypeCachingBytecodeGenerator.java:32)
	at org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker.createMockType(SubclassByteBuddyMockMaker.java:71)
	at org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker.createMock(SubclassByteBuddyMockMaker.java:42)
	at org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker.createMock(ByteBuddyMockMaker.java:25)
	at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:35)
	at org.mockito.internal.MockitoCore.mock(MockitoCore.java:62)
	at org.mockito.Mockito.mock(Mockito.java:1907)
	at org.mockito.Mockito.mock(Mockito.java:1816)
	... 37 more
Caused by: java.lang.UnsupportedOperationException: can't load this type of class file
	at java.lang.ClassLoader.defineClass(ClassLoader.java:591)
	at java.lang.reflect.Method.invoke(Native Method)
	at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Direct.defineClass(ClassInjector.java:685)
	... 57 more

// Act
viewModel.clearCaches(mockWebView)

// Assert
verify(mockWebView).clearCache(true)
}

private fun generateExpectedAuthorizationUrl(
server: String,
codeChallenge: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,42 @@ class LoginViewActivityTest {
Assert.assertTrue("Reload should be called.", reloadCalled)
}

@Test
fun topAppBar_ClearCachesButton_ClearsCachesAndReloads() {
var clearCachesCalled = false
var reloadCalled = false
androidComposeTestRule.setContent {
DefaultTopAppBarTestWrapper(
clearCaches = { clearCachesCalled = true },
reloadWebView = { reloadCalled = true },
)
}

val backButton = androidComposeTestRule.onNodeWithContentDescription(
androidComposeTestRule.activity.getString(R.string.sf__back_button_content_description)
)
val titleText = androidComposeTestRule.onNodeWithText(DEFAULT_URL)
val menu = androidComposeTestRule.onNodeWithContentDescription(
androidComposeTestRule.activity.getString(R.string.sf__more_options)
)
val clearCachesButton = androidComposeTestRule.onNodeWithText(
androidComposeTestRule.activity.getString(R.string.sf__clear_caches)
)

backButton.assertDoesNotExist()
titleText.assertIsDisplayed()
menu.assertIsDisplayed()

menu.performClick()
clearCachesButton.assertIsDisplayed()
Assert.assertFalse("Clear caches should not be called yet.", clearCachesCalled)
Assert.assertFalse("Reload should not be called yet.", reloadCalled)

clearCachesButton.performClick()
Assert.assertTrue("Clear caches should be called.", clearCachesCalled)
Assert.assertTrue("Reload should be called.", reloadCalled)
}

@Test
fun topAppBar_ReloadButton_CallsReload() {
var reloadCalled = false
Expand Down Expand Up @@ -363,13 +399,14 @@ class LoginViewActivityTest {
titleTextColor: Color = Color.Black,
showServerPicker: MutableState<Boolean> = remember { mutableStateOf(false) },
clearCookies: () -> Unit = { },
clearCaches: () -> Unit = { },
reloadWebView: () -> Unit = { },
shouldShowBackButton: Boolean = false,
finish: () -> Unit = { },
) {
DefaultTopAppBar(
backgroundColor, titleText, titleTextColor, showServerPicker, clearCookies,
reloadWebView, shouldShowBackButton, finish
clearCaches, reloadWebView, shouldShowBackButton, finish
)
}

Expand Down
Loading