fix: 首次提交

This commit is contained in:
2024-12-09 11:25:23 +08:00
parent d0c01071e9
commit 2c2109a5f3
4741 changed files with 290641 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
//
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package {
default_applicable_licenses: ["frameworks_base_license"],
}
android_test {
name: "SpaLibTests",
test_suites: ["device-tests"],
srcs: [
":SpaLib_srcs",
"src/**/*.kt",
],
use_resource_processor: true,
static_libs: [
"SpaLib",
"SpaLibTestUtils",
"androidx.compose.runtime_runtime",
"androidx.test.ext.junit",
"androidx.test.runner",
"mockito-target-minus-junit4",
],
kotlincflags: ["-Xjvm-default=all"],
sdk_version: "current",
min_sdk_version: "31",
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.spa.test">
<uses-sdk android:minSdkVersion="21"/>
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:label="Tests for SpaLib"
android:targetPackage="com.android.settingslib.spa.test">
</instrumentation>
</manifest>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="412dp"
android:height="300dp"
android:viewportWidth="412"
android:viewportHeight="300">
<path
android:pathData="M383.9,300H28.1C12.6,300 0,287.4 0,271.9V28.1C0,12.6 12.6,0 28.1,0h355.8C399.4,0 412,12.6 412,28.1v243.8C412,287.4 399.4,300 383.9,300z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M79.2,179.6h53.6v8.5h-53.6z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M142.5,179.6h30.4v8.5h-30.4z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M79.2,195.5h79.2v8.5h-79.2z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M168.1,195.5h34.1v8.5h-34.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M211.9,195.5h34.1v8.5h-34.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M182.7,179.6h73.1v8.5h-73.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M265.5,179.6h26.8v8.5h-26.8z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M302.1,179.6h26.8v8.5h-26.8z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M142.7,67.9h-11.5c-1.6,0 -2.9,1.3 -2.9,2.9H67.8c-7.9,0 -14.4,6.5 -14.4,14.4v132.4c0,7.9 6.5,14.4 14.4,14.4h276.4c7.9,0 14.4,-6.5 14.4,-14.4V85.2c0,-7.9 -6.5,-14.4 -14.4,-14.4H203.1c0,-1.6 -1.3,-2.9 -2.9,-2.9h-28.8c-1.6,0 -2.9,1.3 -2.9,2.9h-23C145.5,69.2 144.3,67.9 142.7,67.9zM344.2,73.7c6.4,0 11.5,5.2 11.5,11.5v132.4c0,6.3 -5.2,11.5 -11.5,11.5H67.8c-6.4,0 -11.5,-5.2 -11.5,-11.5V85.2c0,-6.3 5.2,-11.5 11.5,-11.5H344.2z"
android:fillColor="#DADCE0"/>
</vector>

File diff suppressed because it is too large Load Diff

View File

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="test_quantity_strings">{count, plural,
=1 {There is one song found.}
other {There are # songs found.}
}</string>
<string name="test_quantity_strings_with_param">{count, plural,
=1 {There is one song found in {place}.}
other {There are # songs found in {place}.}
}</string>
<string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string>
<string name="test_link"><a href="https://www.android.com/">link</a></string>
</resources>

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework
import android.content.Context
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SppDialog
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
import com.android.settingslib.spa.tests.testutils.SppDisabled
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.testutils.waitUntil
import com.android.settingslib.spa.testutils.waitUntilExists
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BrowseActivityTest {
@get:Rule
val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaLogger = SpaLoggerForTest()
@Test
fun browseContent_onNavigate_logPageEvent() {
val spaEnvironment = SpaEnvironmentForTest(
context = context,
rootPages = listOf(SppHome.createSettingsPage()),
logger = spaLogger,
)
SpaEnvironmentFactory.reset(spaEnvironment)
val sppRepository by spaEnvironment.pageProviderRepository
val sppHome = sppRepository.getProviderOrNull("SppHome")!!
val pageHome = sppHome.createSettingsPage()
val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!!
val pageLayer1 = sppLayer1.createSettingsPage()
composeTestRule.setContent {
BrowseContent(
sppRepository = sppRepository,
isPageEnabled = SettingsPage::isEnabled,
initialIntent = null,
)
}
composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed()
spaLogger.verifyPageEvent(pageHome.id, 1, 0)
spaLogger.verifyPageEvent(pageLayer1.id, 0, 0)
// click to layer1 page
composeTestRule.onNodeWithText("SppHome to Layer1").assertIsDisplayed().performClick()
waitUntil {
composeTestRule.onAllNodesWithText(sppLayer1.getTitle(null))
.fetchSemanticsNodes().size == 1
}
spaLogger.verifyPageEvent(pageHome.id, 1, 1)
spaLogger.verifyPageEvent(pageLayer1.id, 1, 0)
}
@Test
fun browseContent_whenDisabled_noLogPageEvent() {
val spaEnvironment = SpaEnvironmentForTest(
context = context,
rootPages = listOf(SppDisabled.createSettingsPage()),
logger = spaLogger,
)
SpaEnvironmentFactory.reset(spaEnvironment)
val sppRepository by spaEnvironment.pageProviderRepository
val sppDisabled = sppRepository.getProviderOrNull("SppDisabled")!!
val pageDisabled = sppDisabled.createSettingsPage()
composeTestRule.setContent {
BrowseContent(
sppRepository = sppRepository,
isPageEnabled = SettingsPage::isEnabled,
initialIntent = null,
)
}
composeTestRule.onNodeWithText(sppDisabled.getTitle(null)).assertDoesNotExist()
spaLogger.verifyPageEvent(pageDisabled.id, 0, 0)
}
@Test
fun browseContent_dialog() {
val spaEnvironment = SpaEnvironmentForTest(
context = context,
rootPages = listOf(SppHome.createSettingsPage()),
logger = spaLogger,
)
SpaEnvironmentFactory.reset(spaEnvironment)
val sppRepository by spaEnvironment.pageProviderRepository
composeTestRule.setContent {
BrowseContent(
sppRepository = sppRepository,
isPageEnabled = SettingsPage::isEnabled,
initialIntent = null,
)
}
composeTestRule.onNodeWithText(SppDialog.name).performClick()
composeTestRule.waitUntilExists(hasText(SppDialog.CONTENT))
}
}
private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {
assertThat(getEventCount(id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK)).isEqualTo(entryCount)
assertThat(getEventCount(id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK)).isEqualTo(leaveCount)
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.common
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.util.genEntryId
import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppDialog
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer1
import com.android.settingslib.spa.tests.testutils.SppLayer2
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsEntryRepositoryTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaEnvironment =
SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
private val entryRepository by spaEnvironment.entryRepository
@Test
fun testGetPageWithEntry() {
val pageWithEntry = entryRepository.getAllPageWithEntry()
assertThat(pageWithEntry).hasSize(4)
assertThat(entryRepository.getPageWithEntry(genPageId("SppHome"))?.entries)
.hasSize(2)
assertThat(entryRepository.getPageWithEntry(genPageId("SppLayer1"))?.entries)
.hasSize(3)
assertThat(entryRepository.getPageWithEntry(genPageId("SppLayer2"))?.entries)
.hasSize(2)
assertThat(entryRepository.getPageWithEntry(genPageId("SppWithParam"))).isNull()
}
@Test
fun testGetEntry() {
val entry = entryRepository.getAllEntries()
assertThat(entry).hasSize(8)
assertThat(
entryRepository.getEntry(
genEntryId(
"ROOT",
SppHome.createSettingsPage(),
NullPageProvider.createSettingsPage(),
SppHome.createSettingsPage(),
)
)
).isNotNull()
assertThat(
entryRepository.getEntry(
genEntryId(
"INJECT",
SppLayer1.createSettingsPage(),
SppHome.createSettingsPage(),
SppLayer1.createSettingsPage(),
)
)
).isNotNull()
assertThat(
entryRepository.getEntry(
genEntryId(
"INJECT",
SppLayer2.createSettingsPage(),
SppLayer1.createSettingsPage(),
SppLayer2.createSettingsPage(),
)
)
).isNotNull()
assertThat(
entryRepository.getEntry(
genEntryId(
"INJECT",
SppDialog.createSettingsPage(),
SppHome.createSettingsPage(),
SppDialog.createSettingsPage(),
)
)
).isNotNull()
assertThat(
entryRepository.getEntry(
genEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
)
).isNotNull()
assertThat(
entryRepository.getEntry(
genEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
)
).isNotNull()
assertThat(
entryRepository.getEntry(
genEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
)
).isNotNull()
assertThat(
entryRepository.getEntry(
genEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
)
).isNotNull()
}
@Test
fun testGetEntryPath() {
SpaEnvironmentFactory.reset(spaEnvironment)
assertThat(
entryRepository.getEntryPathWithLabel(
genEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
)
).containsExactly("Layer2Entry1", "INJECT_SppLayer2", "INJECT_SppLayer1", "ROOT_SppHome")
.inOrder()
assertThat(
entryRepository.getEntryPathWithTitle(
genEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
"entryTitle"
)
).containsExactly("entryTitle", "SppLayer2", "TitleLayer1", "TitleHome").inOrder()
assertThat(
entryRepository.getEntryPathWithLabel(
genEntryId(
"INJECT",
SppLayer1.createSettingsPage(),
SppHome.createSettingsPage(),
SppLayer1.createSettingsPage(),
)
)
).containsExactly("INJECT_SppLayer1", "ROOT_SppHome").inOrder()
assertThat(
entryRepository.getEntryPathWithTitle(
genEntryId(
"INJECT",
SppLayer2.createSettingsPage(),
SppLayer1.createSettingsPage(),
SppLayer2.createSettingsPage(),
),
"defaultTitle"
)
).containsExactly("SppLayer2", "TitleLayer1", "TitleHome").inOrder()
}
}

View File

@@ -0,0 +1,232 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.common
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.os.bundleOf
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.util.genEntryId
import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.slice.appendSpaParams
import com.android.settingslib.spa.slice.getEntryId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
const val INJECT_ENTRY_NAME_TEST = "INJECT"
const val ROOT_ENTRY_NAME_TEST = "ROOT"
class MacroForTest(private val pageId: String, private val entryId: String) : EntryMacro {
@Composable
override fun UiLayout() {
val entryData = LocalEntryDataProvider.current
assertThat(entryData.isHighlighted).isFalse()
assertThat(entryData.pageId).isEqualTo(pageId)
assertThat(entryData.entryId).isEqualTo(entryId)
}
override fun getSearchData(): EntrySearchData {
return EntrySearchData("myTitle")
}
override fun getStatusData(): EntryStatusData {
return EntryStatusData(isDisabled = true, isSwitchOff = true)
}
}
@RunWith(AndroidJUnit4::class)
class SettingsEntryTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaEnvironment = SpaEnvironmentForTest(context)
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testBuildBasic() {
val owner = createSettingsPage("mySpp")
val entry = SettingsEntryBuilder.create(owner, "myEntry").build()
assertThat(entry.id).isEqualTo(genEntryId("myEntry", owner))
assertThat(entry.label).isEqualTo("myEntry")
assertThat(entry.owner.sppName).isEqualTo("mySpp")
assertThat(entry.owner.displayName).isEqualTo("mySpp")
assertThat(entry.fromPage).isNull()
assertThat(entry.toPage).isNull()
assertThat(entry.isAllowSearch).isFalse()
assertThat(entry.isSearchDataDynamic).isFalse()
assertThat(entry.hasMutableStatus).isFalse()
assertThat(entry.hasSliceSupport).isFalse()
}
@Test
fun testBuildWithLink() {
val owner = createSettingsPage("mySpp")
val fromPage = createSettingsPage("fromSpp")
val toPage = createSettingsPage("toSpp")
val entryFrom =
SettingsEntryBuilder.createLinkFrom("myEntry", owner).setLink(toPage = toPage).build()
assertThat(entryFrom.id).isEqualTo(genEntryId("myEntry", owner, owner, toPage))
assertThat(entryFrom.label).isEqualTo("myEntry")
assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
val entryTo =
SettingsEntryBuilder.createLinkTo("myEntry", owner).setLink(fromPage = fromPage).build()
assertThat(entryTo.id).isEqualTo(genEntryId("myEntry", owner, fromPage, owner))
assertThat(entryTo.label).isEqualTo("myEntry")
assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
assertThat(entryTo.toPage!!.sppName).isEqualTo("mySpp")
}
@Test
fun testBuildInject() {
val owner = createSettingsPage("mySpp")
val entryInject = SettingsEntryBuilder.createInject(owner).build()
assertThat(entryInject.id).isEqualTo(
genEntryId(
INJECT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
assertThat(entryInject.label).isEqualTo("${INJECT_ENTRY_NAME_TEST}_mySpp")
assertThat(entryInject.fromPage).isNull()
assertThat(entryInject.toPage).isNotNull()
}
@Test
fun testBuildRoot() {
val owner = createSettingsPage("mySpp")
val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
assertThat(entryInject.id).isEqualTo(
genEntryId(
ROOT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
assertThat(entryInject.label).isEqualTo("myRootEntry")
assertThat(entryInject.fromPage).isNull()
assertThat(entryInject.toPage).isNotNull()
}
@Test
fun testSetAttributes() {
SpaEnvironmentFactory.reset(spaEnvironment)
val owner = createSettingsPage("SppHome")
val entryBuilder =
SettingsEntryBuilder.create(owner, "myEntry")
.setLabel("myEntryDisplay")
.setIsSearchDataDynamic(false)
.setHasMutableStatus(true)
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entry = entryBuilder.build()
assertThat(entry.id).isEqualTo(genEntryId("myEntry", owner))
assertThat(entry.label).isEqualTo("myEntryDisplay")
assertThat(entry.fromPage).isNull()
assertThat(entry.toPage).isNull()
assertThat(entry.isAllowSearch).isTrue()
assertThat(entry.isSearchDataDynamic).isFalse()
assertThat(entry.hasMutableStatus).isTrue()
assertThat(entry.hasSliceSupport).isTrue()
// Test disabled Spp
val ownerDisabled = createSettingsPage("SppDisabled")
val entryBuilderDisabled =
SettingsEntryBuilder.create(ownerDisabled, "myEntry")
.setLabel("myEntryDisplay")
.setIsSearchDataDynamic(false)
.setHasMutableStatus(true)
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entryDisabled = entryBuilderDisabled.build()
assertThat(entryDisabled.id).isEqualTo(genEntryId("myEntry", ownerDisabled))
assertThat(entryDisabled.label).isEqualTo("myEntryDisplay")
assertThat(entryDisabled.fromPage).isNull()
assertThat(entryDisabled.toPage).isNull()
assertThat(entryDisabled.isAllowSearch).isFalse()
assertThat(entryDisabled.isSearchDataDynamic).isFalse()
assertThat(entryDisabled.hasMutableStatus).isTrue()
assertThat(entryDisabled.hasSliceSupport).isFalse()
// Clear search data fn
val entry2 = entryBuilder.clearSearchDataFn().build()
assertThat(entry2.isAllowSearch).isFalse()
// Clear SppHome in spa environment
SpaEnvironmentFactory.reset()
val entry3 = entryBuilder.build()
assertThat(entry3.id).isEqualTo(genEntryId("myEntry", owner))
assertThat(entry3.label).isEqualTo("myEntryDisplay")
assertThat(entry3.fromPage).isNull()
assertThat(entry3.toPage).isNull()
assertThat(entry3.isAllowSearch).isFalse()
assertThat(entry3.isSearchDataDynamic).isFalse()
assertThat(entry3.hasMutableStatus).isTrue()
assertThat(entry3.hasSliceSupport).isFalse()
}
@Test
fun testSetMarco() {
SpaEnvironmentFactory.reset(spaEnvironment)
val owner = createSettingsPage("SppHome", arguments = bundleOf("param" to "v1"))
val entry = SettingsEntryBuilder.create(owner, "myEntry").setMacro {
assertThat(it?.getString("param")).isEqualTo("v1")
assertThat(it?.getString("rtParam")).isEqualTo("v2")
assertThat(it?.getString("unknown")).isNull()
MacroForTest(genPageId("SppHome"), genEntryId("myEntry", owner))
}.build()
val rtArguments = bundleOf("rtParam" to "v2")
composeTestRule.setContent { entry.UiLayout(rtArguments) }
assertThat(entry.isAllowSearch).isTrue()
assertThat(entry.isSearchDataDynamic).isFalse()
assertThat(entry.hasMutableStatus).isFalse()
assertThat(entry.hasSliceSupport).isFalse()
val searchData = entry.getSearchData(rtArguments)
val statusData = entry.getStatusData(rtArguments)
assertThat(searchData?.title).isEqualTo("myTitle")
assertThat(searchData?.keyword).isEmpty()
assertThat(statusData?.isDisabled).isTrue()
assertThat(statusData?.isSwitchOff).isTrue()
}
@Test
fun testSetSliceDataFn() {
SpaEnvironmentFactory.reset(spaEnvironment)
val owner = createSettingsPage("SppHome")
val entryId = genEntryId("myEntry", owner)
val emptySliceData = EntrySliceData()
val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry").setSliceDataFn { uri, _ ->
return@setSliceDataFn if (uri.getEntryId() == entryId) emptySliceData else null
}
val entry = entryBuilder.build()
assertThat(entry.id).isEqualTo(entryId)
assertThat(entry.hasSliceSupport).isTrue()
assertThat(entry.getSliceData(Uri.EMPTY)).isNull()
assertThat(
entry.getSliceData(
Uri.Builder().scheme("content").appendSpaParams(entryId = entryId).build()
)
).isEqualTo(emptySliceData)
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.common
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsPageProviderRepositoryTest {
@Test
fun rootPages_empty() {
val sppRepoEmpty = SettingsPageProviderRepository(emptyList())
assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
}
@Test
fun rootPages_single() {
val nullPage = NullPageProvider.createSettingsPage()
val sppRepoNull = SettingsPageProviderRepository(
allPageProviders = emptyList(),
rootPages = listOf(nullPage),
)
assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
assertThat(sppRepoNull.getAllRootPages()).containsExactly(nullPage)
}
@Test
fun rootPages_twoPages() {
val rootPage1 = createSettingsPage(sppName = "Spp1", displayName = "Spp1")
val rootPage2 = createSettingsPage(sppName = "Spp2", displayName = "Spp2")
val sppRepo = SettingsPageProviderRepository(
allPageProviders = emptyList(),
rootPages = listOf(rootPage1, rootPage2),
)
assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
assertThat(sppRepo.getAllRootPages()).containsExactly(rootPage1, rootPage2)
}
@Test
fun getProviderOrNull_empty() {
val sppRepoEmpty = SettingsPageProviderRepository(emptyList())
assertThat(sppRepoEmpty.getAllProviders()).isEmpty()
assertThat(sppRepoEmpty.getProviderOrNull("Spp")).isNull()
}
@Test
fun getProviderOrNull_single() {
val sppRepo = SettingsPageProviderRepository(listOf(
object : SettingsPageProvider {
override val name = "Spp"
}
))
assertThat(sppRepo.getAllProviders()).hasSize(1)
assertThat(sppRepo.getProviderOrNull("Spp")).isNotNull()
assertThat(sppRepo.getProviderOrNull("SppUnknown")).isNull()
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.common
import android.content.Context
import androidx.core.os.bundleOf
import androidx.navigation.NavType
import androidx.navigation.navArgument
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsPageTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaEnvironment = SpaEnvironmentForTest(context)
@Test
fun testNullPage() {
val page = NullPageProvider.createSettingsPage()
assertThat(page.id).isEqualTo(genPageId("NULL"))
assertThat(page.sppName).isEqualTo("NULL")
assertThat(page.displayName).isEqualTo("NULL")
assertThat(page.buildRoute()).isEqualTo("NULL")
assertThat(page.isCreateBy("NULL")).isTrue()
assertThat(page.isCreateBy("Spp")).isFalse()
assertThat(page.isBrowsable()).isFalse()
}
@Test
fun testRegularPage() {
val page = createSettingsPage("mySpp", "SppDisplayName")
assertThat(page.id).isEqualTo(genPageId("mySpp"))
assertThat(page.sppName).isEqualTo("mySpp")
assertThat(page.displayName).isEqualTo("SppDisplayName")
assertThat(page.buildRoute()).isEqualTo("mySpp")
assertThat(page.isCreateBy("NULL")).isFalse()
assertThat(page.isCreateBy("mySpp")).isTrue()
assertThat(page.isBrowsable()).isTrue()
}
@Test
fun testParamPage() {
val arguments = bundleOf(
"string_param" to "myStr",
"int_param" to 10,
)
val page = spaEnvironment.createPage("SppWithParam", arguments)
assertThat(page.id).isEqualTo(
genPageId(
"SppWithParam", listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
), arguments
)
)
assertThat(page.sppName).isEqualTo("SppWithParam")
assertThat(page.displayName).isEqualTo("SppWithParam/myStr/10")
assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
assertThat(page.isCreateBy("SppWithParam")).isTrue()
assertThat(page.isBrowsable()).isTrue()
}
@Test
fun testRtParamPage() {
val arguments = bundleOf(
"string_param" to "myStr",
"int_param" to 10,
"rt_param" to "rtStr",
)
val page = spaEnvironment.createPage("SppWithRtParam", arguments)
assertThat(page.id).isEqualTo(
genPageId(
"SppWithRtParam", listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
navArgument("rt_param") { type = NavType.StringType },
), arguments
)
)
assertThat(page.sppName).isEqualTo("SppWithRtParam")
assertThat(page.displayName).isEqualTo("SppWithRtParam/myStr/10")
assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
assertThat(page.isBrowsable()).isFalse()
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.common
import android.content.Context
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.google.common.truth.Truth
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SpaEnvironmentTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaEnvironment = SpaEnvironmentForTest(context)
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testSpaEnvironmentFactory() {
SpaEnvironmentFactory.reset()
Truth.assertThat(SpaEnvironmentFactory.isReady()).isFalse()
Assert.assertThrows(UnsupportedOperationException::class.java) {
SpaEnvironmentFactory.instance
}
SpaEnvironmentFactory.reset(spaEnvironment)
Truth.assertThat(SpaEnvironmentFactory.isReady()).isTrue()
Truth.assertThat(SpaEnvironmentFactory.instance).isEqualTo(spaEnvironment)
}
@Test
fun testSpaEnvironmentFactoryForPreview() {
SpaEnvironmentFactory.reset()
composeTestRule.setContent {
Truth.assertThat(SpaEnvironmentFactory.isReady()).isFalse()
SpaEnvironmentFactory.resetForPreview()
Truth.assertThat(SpaEnvironmentFactory.isReady()).isTrue()
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.compose
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class KeyboardsTest {
@get:Rule
val composeTestRule = createComposeRule()
@get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
lateinit var keyboardController: SoftwareKeyboardController
@Test
fun hideKeyboardAction_callControllerHide() {
lateinit var action: () -> Unit
composeTestRule.setContent {
CompositionLocalProvider(LocalSoftwareKeyboardController provides keyboardController) {
action = hideKeyboardAction()
}
}
action()
verify(keyboardController).hide()
}
@Test
fun rememberLazyListStateAndHideKeyboardWhenStartScroll_notCallHideInitially() {
setLazyColumn(scroll = false)
verify(keyboardController, never()).hide()
}
@Test
fun rememberLazyListStateAndHideKeyboardWhenStartScroll_callHideWhenScroll() {
setLazyColumn(scroll = true)
verify(keyboardController).hide()
}
private fun setLazyColumn(scroll: Boolean) {
composeTestRule.setContent {
CompositionLocalProvider(LocalSoftwareKeyboardController provides keyboardController) {
val lazyListState = rememberLazyListStateAndHideKeyboardWhenStartScroll()
LazyColumn(
modifier = Modifier.size(100.dp),
state = lazyListState,
) {
items(count = 10) {
Text(text = it.toString())
}
}
if (scroll) {
LaunchedEffect(Unit) {
lazyListState.animateScrollToItem(1)
}
}
}
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.compose
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LifecycleEffectTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun onStart_isCalled() {
var onStartIsCalled = false
composeTestRule.setContent {
LifecycleEffect(onStart = { onStartIsCalled = true })
}
assertThat(onStartIsCalled).isTrue()
}
@Test
fun onStop_isCalled() {
var onStopIsCalled = false
val testLifecycleOwner = TestLifecycleOwner()
composeTestRule.setContent {
CompositionLocalProvider(LocalLifecycleOwner provides testLifecycleOwner) {
LifecycleEffect(onStop = { onStopIsCalled = true })
}
LaunchedEffect(Unit) {
testLifecycleOwner.currentState = Lifecycle.State.CREATED
}
}
assertThat(onStopIsCalled).isTrue()
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.compose
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.waitUntilExists
import kotlinx.coroutines.delay
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class NavControllerWrapperTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun navigate_canNavigate() {
composeTestRule.setContent {
TestNavHost {
LocalNavController.current.navigate(ROUTE_B)
}
}
composeTestRule.onNodeWithText(ROUTE_B).assertIsDisplayed()
}
@Test
fun navigate_canNavigateBack() {
composeTestRule.setContent {
TestNavHost {
val navController = LocalNavController.current
LaunchedEffect(Unit) {
navController.navigate(ROUTE_B)
delay(100)
navController.navigateBack()
}
}
}
composeTestRule.waitUntilExists(hasText(ROUTE_A))
}
@Test
fun navigate_canNavigateAndPopUpCurrent() {
composeTestRule.setContent {
TestNavHost {
val navController = LocalNavController.current
LaunchedEffect(Unit) {
navController.navigate(ROUTE_B)
delay(100)
navController.navigate(ROUTE_C, popUpCurrent = true)
delay(100)
navController.navigateBack()
}
}
}
composeTestRule.waitUntilExists(hasText(ROUTE_A))
}
private companion object {
@Composable
fun TestNavHost(content: @Composable () -> Unit) {
val navController = rememberNavController()
CompositionLocalProvider(navController.localNavController()) {
NavHost(navController, ROUTE_A) {
composable(route = ROUTE_A) { Text(ROUTE_A) }
composable(route = ROUTE_B) { Text(ROUTE_B) }
composable(route = ROUTE_C) { Text(ROUTE_C) }
}
content()
}
}
const val ROUTE_A = "RouteA"
const val ROUTE_B = "RouteB"
const val ROUTE_C = "RouteC"
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.compose
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.waitUntilExists
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class OnBackEffectTest {
@get:Rule
val composeTestRule = createComposeRule()
private var onBackEffectCalled = false
@Test
fun onBackEffect() {
composeTestRule.setContent {
TestNavHost {
val navController = LocalNavController.current
LaunchedEffect(Unit) {
navController.navigate(ROUTE_B)
delay(100)
navController.navigateBack()
}
}
}
composeTestRule.waitUntilExists(hasText(ROUTE_A))
assertThat(onBackEffectCalled).isTrue()
}
@Composable
private fun TestNavHost(content: @Composable () -> Unit) {
val navController = rememberNavController()
CompositionLocalProvider(navController.localNavController()) {
NavHost(navController, ROUTE_A) {
composable(route = ROUTE_A) { Text(ROUTE_A) }
composable(route = ROUTE_B) {
Text(ROUTE_B)
OnBackEffect {
onBackEffectCalled = true
}
}
}
content()
}
}
private companion object {
const val ROUTE_A = "RouteA"
const val ROUTE_B = "RouteB"
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.compose
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withTimeout
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class OverridableFlowTest {
@Test
fun noOverride() = runTest {
val overridableFlow = OverridableFlow(flowOf(true))
launch {
val values = collectValues(overridableFlow.flow)
assertThat(values).containsExactly(true)
}
}
@Test
fun whenOverride() = runTest {
val overridableFlow = OverridableFlow(flowOf(true))
overridableFlow.override(false)
launch {
val values = collectValues(overridableFlow.flow)
assertThat(values).containsExactly(true, false).inOrder()
}
}
private suspend fun <T> collectValues(flow: Flow<T>): List<T> = withTimeout(500) {
val flowValues = mutableListOf<T>()
flow.toList(flowValues)
flowValues
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.theme
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import androidx.compose.ui.graphics.Color
@RunWith(AndroidJUnit4::class)
class SettingsColorsTest {
private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun testDynamicTheme() {
// The dynamic color could be different in different device, just check basic restrictions:
// 1. text color is different with background color
// 2. primary / spinner color is different with its on-item color
val ls = dynamicLightColorScheme(context)
assertThat(ls.categoryTitle).isNotEqualTo(ls.background)
assertThat(ls.secondaryText).isNotEqualTo(ls.background)
assertThat(ls.primaryContainer).isNotEqualTo(ls.onPrimaryContainer)
assertThat(ls.spinnerHeaderContainer).isNotEqualTo(ls.onSpinnerHeaderContainer)
assertThat(ls.spinnerItemContainer).isNotEqualTo(ls.onSpinnerItemContainer)
val ds = dynamicDarkColorScheme(context)
assertThat(ds.categoryTitle).isNotEqualTo(ds.background)
assertThat(ds.secondaryText).isNotEqualTo(ds.background)
assertThat(ds.primaryContainer).isNotEqualTo(ds.onPrimaryContainer)
assertThat(ds.spinnerHeaderContainer).isNotEqualTo(ds.onSpinnerHeaderContainer)
assertThat(ds.spinnerItemContainer).isNotEqualTo(ds.onSpinnerItemContainer)
}
@Test
fun testStaticTheme() {
val ls = lightColorScheme()
assertThat(ls.background).isEqualTo(Color(red = 244, green = 239, blue = 244))
assertThat(ls.categoryTitle).isEqualTo(Color(red = 103, green = 80, blue = 164))
assertThat(ls.surface).isEqualTo(Color(red = 255, green = 251, blue = 254))
assertThat(ls.surfaceHeader).isEqualTo(Color(red = 230, green = 225, blue = 229))
assertThat(ls.secondaryText).isEqualTo(Color(red = 73, green = 69, blue = 79))
assertThat(ls.primaryContainer).isEqualTo(Color(red = 234, green = 221, blue = 255))
assertThat(ls.onPrimaryContainer).isEqualTo(Color(red = 28, green = 27, blue = 31))
assertThat(ls.spinnerHeaderContainer).isEqualTo(Color(red = 234, green = 221, blue = 255))
assertThat(ls.onSpinnerHeaderContainer).isEqualTo(Color(red = 28, green = 27, blue = 31))
assertThat(ls.spinnerItemContainer).isEqualTo(Color(red = 232, green = 222, blue = 248))
assertThat(ls.onSpinnerItemContainer).isEqualTo(Color(red = 73, green = 69, blue = 79))
val ds = darkColorScheme()
assertThat(ds.background).isEqualTo(Color(red = 28, green = 27, blue = 31))
assertThat(ds.categoryTitle).isEqualTo(Color(red = 234, green = 221, blue = 255))
assertThat(ds.surface).isEqualTo(Color(red = 49, green = 48, blue = 51))
assertThat(ds.surfaceHeader).isEqualTo(Color(red = 72, green = 70, blue = 73))
assertThat(ds.secondaryText).isEqualTo(Color(red = 202, green = 196, blue = 208))
assertThat(ds.primaryContainer).isEqualTo(Color(red = 232, green = 222, blue = 248))
assertThat(ds.onPrimaryContainer).isEqualTo(Color(red = 28, green = 27, blue = 31))
assertThat(ds.spinnerHeaderContainer).isEqualTo(Color(red = 234, green = 221, blue = 255))
assertThat(ds.onSpinnerHeaderContainer).isEqualTo(Color(red = 28, green = 27, blue = 31))
assertThat(ds.spinnerItemContainer).isEqualTo(Color(red = 232, green = 222, blue = 248))
assertThat(ds.onSpinnerItemContainer).isEqualTo(Color(red = 73, green = 69, blue = 79))
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.theme
import android.content.Context
import android.content.res.Resources
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Typography
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.text.font.FontFamily
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class SettingsThemeTest {
@get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@get:Rule
val composeTestRule = createComposeRule()
@Mock
private lateinit var context: Context
@Mock
private lateinit var resources: Resources
private var nextMockResId = 1
@Before
fun setUp() {
whenever(context.resources).thenReturn(resources)
whenever(resources.getString(any())).thenReturn("")
}
private fun mockAndroidConfig(configName: String, configValue: String) {
whenever(resources.getIdentifier(configName, "string", "android"))
.thenReturn(nextMockResId)
whenever(resources.getString(nextMockResId)).thenReturn(configValue)
nextMockResId++
}
@Test
fun noFontFamilyConfig_useDefaultFontFamily() {
val fontFamily = getFontFamily()
assertThat(fontFamily.headlineLarge.fontFamily).isSameInstanceAs(FontFamily.Default)
assertThat(fontFamily.bodyLarge.fontFamily).isSameInstanceAs(FontFamily.Default)
}
@Test
fun hasFontFamilyConfig_useConfiguredFontFamily() {
mockAndroidConfig("config_headlineFontFamily", "HeadlineNormal")
mockAndroidConfig("config_headlineFontFamilyMedium", "HeadlineMedium")
mockAndroidConfig("config_bodyFontFamily", "BodyNormal")
mockAndroidConfig("config_bodyFontFamilyMedium", "BodyMedium")
val fontFamily = getFontFamily()
val headlineFontFamily = fontFamily.headlineLarge.fontFamily.toString()
assertThat(headlineFontFamily).contains("HeadlineNormal")
assertThat(headlineFontFamily).contains("HeadlineMedium")
val bodyFontFamily = fontFamily.bodyLarge.fontFamily.toString()
assertThat(bodyFontFamily).contains("BodyNormal")
assertThat(bodyFontFamily).contains("BodyMedium")
}
private fun getFontFamily(): Typography {
lateinit var typography: Typography
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
SettingsTheme {
typography = MaterialTheme.typography
}
}
}
return typography
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.util
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.test.R
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AnnotatedStringResourceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testAnnotatedStringResource() {
composeTestRule.setContent {
val annotatedString =
annotatedStringResource(R.string.test_annotated_string_resource)
val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
assertThat(annotations).containsExactly(
AnnotatedString.Range(
item = "https://www.android.com/",
start = 31,
end = 35,
tag = URL_SPAN_TAG,
)
)
assertThat(annotatedString.spanStyles).containsExactly(
AnnotatedString.Range(
item = SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal),
start = 22,
end = 26,
),
AnnotatedString.Range(
item = SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline,
),
start = 31,
end = 35,
),
)
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.util
import android.content.Context
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.test.R
import com.android.settingslib.spa.widget.ui.AnnotatedText
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class AnnotatedTextTest {
@get:Rule
val composeTestRule = createComposeRule()
@get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
private lateinit var uriHandler: UriHandler
private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun text_isDisplayed() {
composeTestRule.setContent {
AnnotatedText(R.string.test_annotated_string_resource)
}
composeTestRule.onNodeWithText(context.getString(R.string.test_annotated_string_resource))
.assertIsDisplayed()
}
@Test
fun onUriClick_openUri() {
composeTestRule.setContent {
CompositionLocalProvider(LocalUriHandler provides uriHandler) {
AnnotatedText(R.string.test_link)
}
}
composeTestRule.onNodeWithText(context.getString(R.string.test_link)).performClick()
verify(uriHandler).openUri("https://www.android.com/")
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.util
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CollectionsTest {
@Test
fun testAsyncForEach() = runTest {
var sum = 0
listOf(1, 2, 3).asyncForEach { sum += it }
Truth.assertThat(sum).isEqualTo(6)
}
@Test
fun testAsyncFilter() = runTest {
val res = listOf(1, 2, 3).asyncFilter { it >= 2 }
Truth.assertThat(res).containsExactly(2, 3).inOrder()
}
@Test
fun testAsyncMap() = runTest {
val res = listOf(1, 2, 3).asyncMap { it + 1 }
Truth.assertThat(res).containsExactly(2, 3, 4).inOrder()
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.util
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.waitUntil
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.count
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class FlowsTest {
@Test
fun mapItem() = runTest {
val inputFlow = flowOf(listOf("A", "BB", "CCC"))
val outputFlow = inputFlow.mapItem { it.length }
assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder()
}
@Test
fun asyncMapItem() = runTest {
val inputFlow = flowOf(listOf("A", "BB", "CCC"))
val outputFlow = inputFlow.asyncMapItem { it.length }
assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder()
}
@Test
fun filterItem() = runTest {
val inputFlow = flowOf(listOf("A", "BB", "CCC"))
val outputFlow = inputFlow.filterItem { it.length >= 2 }
assertThat(outputFlow.first()).containsExactly("BB", "CCC").inOrder()
}
@Test
fun waitFirst_otherFlowEmpty() = runTest {
val mainFlow = flowOf("A")
val otherFlow = emptyFlow<String>()
val outputFlow = mainFlow.waitFirst(otherFlow)
assertThat(outputFlow.count()).isEqualTo(0)
}
@Test
fun waitFirst_otherFlowOneValue() = runTest {
val mainFlow = flowOf("A")
val otherFlow = flowOf("B")
val outputFlow = mainFlow.waitFirst(otherFlow)
assertThat(outputFlow.toList()).containsExactly("A")
}
@Test
fun waitFirst_otherFlowTwoValues() = runTest {
val mainFlow = flowOf("A")
val otherFlow = flowOf("B", "B")
val outputFlow = mainFlow.waitFirst(otherFlow)
assertThat(outputFlow.toList()).containsExactly("A")
}
@Test
fun collectLatestWithLifecycle() {
val mainFlow = flowOf("A")
var actual: String? = null
mainFlow.collectLatestWithLifecycle(TestLifecycleOwner()) {
actual = it
}
waitUntil { actual == "A" }
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.util
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.test.R
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MessageFormatsTest {
private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun formatString_one() {
val message = context.formatString(R.string.test_quantity_strings, "count" to 1)
assertThat(message).isEqualTo("There is one song found.")
}
@Test
fun formatString_other() {
val message = context.formatString(R.string.test_quantity_strings, "count" to 2)
assertThat(message).isEqualTo("There are 2 songs found.")
}
@Test
fun formatString_withParam_one() {
val message = context.formatString(
R.string.test_quantity_strings_with_param,
"count" to 1,
"place" to "phone",
)
assertThat(message).isEqualTo("There is one song found in phone.")
}
@Test
fun formatString_withParam_other() {
val message = context.formatString(
R.string.test_quantity_strings_with_param,
"count" to 2,
"place" to "phone",
)
assertThat(message).isEqualTo("There are 2 songs found in phone.")
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.util
import androidx.core.os.bundleOf
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavType
import androidx.navigation.navArgument
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ParameterTest {
@Test
fun navRouteTest() {
val navArguments = listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
)
val route = navArguments.navRoute()
assertThat(route).isEqualTo("/{string_param}/{int_param}")
}
@Test
fun navLinkTest() {
val navArguments = listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
)
val unsetAllLink = navArguments.navLink()
assertThat(unsetAllLink).isEqualTo("/[unset]/[unset]")
val setAllLink = navArguments.navLink(
bundleOf(
"string_param" to "myStr",
"int_param" to 10,
)
)
assertThat(setAllLink).isEqualTo("/myStr/10")
val setUnknownLink = navArguments.navLink(
bundleOf(
"string_param" to "myStr",
"int_param" to 10,
"unknown_param" to "unknown",
)
)
assertThat(setUnknownLink).isEqualTo("/myStr/10")
val setWrongTypeLink = navArguments.navLink(
bundleOf(
"string_param" to "myStr",
"int_param" to "wrongStr",
)
)
assertThat(setWrongTypeLink).isEqualTo("/myStr/0")
}
@Test
fun normalizeTest() {
val emptyArguments = emptyList<NamedNavArgument>()
assertThat(emptyArguments.normalize()).isNull()
val navArguments = listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
navArgument("rt_param") { type = NavType.StringType },
)
val emptyParam = navArguments.normalize()
assertThat(emptyParam).isNotNull()
assertThat(emptyParam.toString()).isEqualTo(
"Bundle[{unset_rt_param=null, unset_string_param=null, unset_int_param=null}]"
)
val setPartialParam = navArguments.normalize(
bundleOf(
"string_param" to "myStr",
"rt_param" to "rtStr",
)
)
assertThat(setPartialParam).isNotNull()
assertThat(setPartialParam.toString()).isEqualTo(
"Bundle[{rt_param=rtStr, string_param=myStr, unset_int_param=null}]"
)
val setAllParam = navArguments.normalize(
bundleOf(
"string_param" to "myStr",
"int_param" to 10,
"rt_param" to "rtStr",
),
eraseRuntimeValues = true
)
assertThat(setAllParam).isNotNull()
assertThat(setAllParam.toString()).isEqualTo(
"Bundle[{rt_param=null, int_param=10, string_param=myStr}]"
)
}
@Test
fun getArgTest() {
val navArguments = listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
)
assertThat(
navArguments.getStringArg(
"string_param", bundleOf(
"string_param" to "myStr",
)
)
).isEqualTo("myStr")
assertThat(
navArguments.getStringArg(
"string_param", bundleOf(
"string_param" to 10,
)
)
).isNull()
assertThat(
navArguments.getStringArg(
"unknown_param", bundleOf(
"string_param" to "myStr",
)
)
).isNull()
assertThat(navArguments.getStringArg("string_param")).isNull()
assertThat(
navArguments.getIntArg(
"int_param", bundleOf(
"int_param" to 10,
)
)
).isEqualTo(10)
assertThat(
navArguments.getIntArg(
"int_param", bundleOf(
"int_param" to "10",
)
)
).isEqualTo(0)
assertThat(
navArguments.getIntArg(
"unknown_param", bundleOf(
"int_param" to 10,
)
)
).isNull()
assertThat(navArguments.getIntArg("int_param")).isNull()
}
@Test
fun isRuntimeParamTest() {
val regularParam = navArgument("regular_param") { type = NavType.StringType }
val rtParam = navArgument("rt_param") { type = NavType.StringType }
assertThat(regularParam.isRuntimeParam()).isFalse()
assertThat(rtParam.isRuntimeParam()).isTrue()
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.util
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.NullPageProvider
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.google.common.truth.Truth
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SpaIntentTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaEnvironment = SpaEnvironmentForTest(context)
@Before
fun setEnvironment() {
SpaEnvironmentFactory.reset(spaEnvironment)
}
@Test
fun testCreateIntent() {
val nullPage = NullPageProvider.createSettingsPage()
Truth.assertThat(nullPage.createIntent()).isNull()
Truth.assertThat(SettingsEntryBuilder.createInject(nullPage).build().createIntent())
.isNull()
val page = spaEnvironment.createPage("SppHome")
val pageIntent = page.createIntent()
Truth.assertThat(pageIntent).isNotNull()
Truth.assertThat(pageIntent!!.getDestination()).isEqualTo(page.buildRoute())
Truth.assertThat(pageIntent.getEntryId()).isNull()
Truth.assertThat(pageIntent.getSessionName()).isNull()
val entry = SettingsEntryBuilder.createInject(page).build()
val entryIntent = entry.createIntent(SESSION_SEARCH)
Truth.assertThat(entryIntent).isNotNull()
Truth.assertThat(entryIntent!!.getDestination()).isEqualTo(page.buildRoute())
Truth.assertThat(entryIntent.getEntryId()).isEqualTo(entry.id)
Truth.assertThat(entryIntent.getSessionName()).isEqualTo(SESSION_SEARCH)
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.util
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class StateFlowBridgeTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun stateFlowBridge_initial() = runTest {
val stateFlowBridge = StateFlowBridge<String>()
val flow = stateFlowBridge.flow
val first = flow.firstWithTimeoutOrNull()
assertThat(first).isNull()
}
@Test
fun stateFlowBridge_setIfAbsent() = runTest {
val stateFlowBridge = StateFlowBridge<String>()
stateFlowBridge.setIfAbsent("A")
val first = stateFlowBridge.flow.firstWithTimeoutOrNull()
assertThat(first).isEqualTo("A")
}
@Test
fun stateFlowBridge_sync() = runTest {
val stateFlowBridge = StateFlowBridge<String>()
composeTestRule.setContent {
stateFlowBridge.Sync { "A" }
}
val first = stateFlowBridge.flow.firstWithTimeoutOrNull()
assertThat(first).isEqualTo("A")
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.framework.util
import androidx.core.os.bundleOf
import androidx.navigation.NavType
import androidx.navigation.navArgument
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class UniqueIdTest {
@Test
fun testUniquePageId() {
Truth.assertThat(genPageId("mySpp")).isEqualTo("1byojwa")
val parameter = listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
)
val arguments = bundleOf(
"string_param" to "myStr",
"int_param" to 10,
)
Truth.assertThat(genPageId("mySppWithParam", parameter, arguments)).isEqualTo("1sz4pbq")
val parameter2 = listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
navArgument("rt_param") { type = NavType.StringType },
)
val arguments2 = bundleOf(
"string_param" to "myStr",
"int_param" to 10,
"rt_param" to "myRtStr",
)
Truth.assertThat(genPageId("mySppWithRtParam", parameter2, arguments2)).isEqualTo("ts6d8k")
}
@Test
fun testUniqueEntryId() {
val owner = createSettingsPage("mySpp")
val fromPage = createSettingsPage("fromSpp")
val toPage = createSettingsPage("toSpp")
Truth.assertThat(genEntryId("myEntry", owner)).isEqualTo("145pppn")
Truth.assertThat(genEntryId("myEntry", owner, fromPage = fromPage, toPage = toPage))
.isEqualTo("1m7jzew")
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.lifecycle
import androidx.compose.material3.Text
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.waitUntilExists
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class FlowExtTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun collectAsCallbackWithLifecycle() {
val flow = flowOf(TEXT)
composeTestRule.setContent {
val callback = flow.collectAsCallbackWithLifecycle()
Text(callback() ?: "")
}
composeTestRule.waitUntilExists(hasText(TEXT))
}
@Test
fun collectAsCallbackWithLifecycle_changed() {
val flow = MutableStateFlow(TEXT)
composeTestRule.setContent {
val callback = flow.collectAsCallbackWithLifecycle()
Text(callback() ?: "")
}
flow.value = NEW_TEXT
composeTestRule.waitUntilExists(hasText(NEW_TEXT))
}
private companion object {
const val TEXT = "Text"
const val NEW_TEXT = "New Text"
}
}

View File

@@ -0,0 +1,332 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.search
import android.content.Context
import android.database.Cursor
import android.os.Bundle
import android.os.Parcel
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppForSearch
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SpaSearchProviderTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaEnvironment =
SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage()))
private val searchProvider = SpaSearchProvider()
private val pageOwner = spaEnvironment.createPage("SppForSearch")
@Test
fun testQueryColumnSetup() {
Truth.assertThat(QueryEnum.SEARCH_STATIC_DATA_QUERY.columnNames)
.containsExactlyElementsIn(QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.columnNames)
Truth.assertThat(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.columnNames)
.containsExactlyElementsIn(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.columnNames)
Truth.assertThat(QueryEnum.SEARCH_STATIC_ROW_QUERY.columnNames)
.containsExactlyElementsIn(QueryEnum.SEARCH_DYNAMIC_ROW_QUERY.columnNames)
}
@Test
fun testQuerySearchStatusData() {
SpaEnvironmentFactory.reset(spaEnvironment)
val immutableStatus = searchProvider.querySearchImmutableStatusData()
Truth.assertThat(immutableStatus.count).isEqualTo(1)
immutableStatus.moveToFirst()
immutableStatus.checkValue(
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
)
immutableStatus.checkValue(
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchDynamicWithImmutableStatus")
)
immutableStatus.checkValue(
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
)
val mutableStatus = searchProvider.querySearchMutableStatusData()
Truth.assertThat(mutableStatus.count).isEqualTo(2)
mutableStatus.moveToFirst()
mutableStatus.checkValue(
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchStaticWithMutableStatus")
)
mutableStatus.checkValue(
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchStaticWithMutableStatus")
)
mutableStatus.checkValue(
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, false.toString()
)
mutableStatus.moveToNext()
mutableStatus.checkValue(
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchDynamicWithMutableStatus")
)
mutableStatus.checkValue(
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchDynamicWithMutableStatus")
)
mutableStatus.checkValue(
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
)
}
@Test
fun testQuerySearchIndexData() {
SpaEnvironmentFactory.reset(spaEnvironment)
val staticData = searchProvider.querySearchStaticData()
Truth.assertThat(staticData.count).isEqualTo(2)
staticData.moveToFirst()
staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchStaticWithNoStatus")
)
staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchStaticWithNoStatus")
)
staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_TITLE, "SearchStaticWithNoStatus"
)
staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_KEYWORD, listOf("").toString()
)
staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY,
ColumnEnum.SEARCH_PATH,
listOf("SearchStaticWithNoStatus", "SppForSearch").toString()
)
staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY,
ColumnEnum.INTENT_TARGET_PACKAGE,
spaEnvironment.appContext.packageName
)
staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY,
ColumnEnum.INTENT_TARGET_CLASS,
"com.android.settingslib.spa.tests.testutils.BlankActivity"
)
// Check extras in intent
val bundle =
staticData.getExtras(QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.INTENT_EXTRAS)
Truth.assertThat(bundle).isNotNull()
Truth.assertThat(bundle!!.size()).isEqualTo(3)
Truth.assertThat(bundle.getString("spaActivityDestination")).isEqualTo("SppForSearch")
Truth.assertThat(bundle.getString("highlightEntry"))
.isEqualTo(pageOwner.getEntryId("SearchStaticWithNoStatus"))
Truth.assertThat(bundle.getString("sessionSource")).isEqualTo("search")
staticData.moveToNext()
staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchStaticWithMutableStatus")
)
staticData.checkValue(
QueryEnum.SEARCH_STATIC_DATA_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchStaticWithMutableStatus")
)
val dynamicData = searchProvider.querySearchDynamicData()
Truth.assertThat(dynamicData.count).isEqualTo(2)
dynamicData.moveToFirst()
dynamicData.checkValue(
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchDynamicWithMutableStatus")
)
dynamicData.checkValue(
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchDynamicWithMutableStatus")
)
dynamicData.moveToNext()
dynamicData.checkValue(
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
)
dynamicData.checkValue(
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchDynamicWithImmutableStatus")
)
dynamicData.checkValue(
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
ColumnEnum.SEARCH_KEYWORD,
listOf("kw1", "kw2").toString()
)
}
@Test
fun testQuerySearchIndexRow() {
SpaEnvironmentFactory.reset(spaEnvironment)
val staticRow = searchProvider.querySearchStaticRow()
Truth.assertThat(staticRow.count).isEqualTo(1)
staticRow.moveToFirst()
staticRow.checkValue(
QueryEnum.SEARCH_STATIC_ROW_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchStaticWithNoStatus")
)
staticRow.checkValue(
QueryEnum.SEARCH_STATIC_ROW_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchStaticWithNoStatus")
)
staticRow.checkValue(
QueryEnum.SEARCH_STATIC_ROW_QUERY, ColumnEnum.SEARCH_TITLE, "SearchStaticWithNoStatus"
)
staticRow.checkValue(
QueryEnum.SEARCH_STATIC_ROW_QUERY, ColumnEnum.SEARCH_KEYWORD, listOf("").toString()
)
staticRow.checkValue(
QueryEnum.SEARCH_STATIC_ROW_QUERY,
ColumnEnum.SEARCH_PATH,
listOf("SearchStaticWithNoStatus", "SppForSearch").toString()
)
staticRow.checkValue(
QueryEnum.SEARCH_STATIC_ROW_QUERY,
ColumnEnum.INTENT_TARGET_PACKAGE,
spaEnvironment.appContext.packageName
)
staticRow.checkValue(
QueryEnum.SEARCH_STATIC_ROW_QUERY,
ColumnEnum.INTENT_TARGET_CLASS,
"com.android.settingslib.spa.tests.testutils.BlankActivity"
)
// Check extras in intent
val bundle =
staticRow.getExtras(QueryEnum.SEARCH_STATIC_ROW_QUERY, ColumnEnum.INTENT_EXTRAS)
Truth.assertThat(bundle).isNotNull()
Truth.assertThat(bundle!!.size()).isEqualTo(3)
Truth.assertThat(bundle.getString("spaActivityDestination")).isEqualTo("SppForSearch")
Truth.assertThat(bundle.getString("highlightEntry"))
.isEqualTo(pageOwner.getEntryId("SearchStaticWithNoStatus"))
Truth.assertThat(bundle.getString("sessionSource")).isEqualTo("search")
Truth.assertThat(
staticRow.getString(
QueryEnum.SEARCH_STATIC_ROW_QUERY.columnNames.indexOf(
ColumnEnum.ENTRY_DISABLED
)
)
).isNull()
val dynamicRow = searchProvider.querySearchDynamicRow()
Truth.assertThat(dynamicRow.count).isEqualTo(3)
dynamicRow.moveToFirst()
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchStaticWithMutableStatus")
)
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchStaticWithMutableStatus")
)
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY, ColumnEnum.ENTRY_DISABLED, false.toString()
)
dynamicRow.moveToNext()
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchDynamicWithMutableStatus")
)
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchDynamicWithMutableStatus")
)
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
)
dynamicRow.moveToNext()
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
ColumnEnum.ENTRY_ID,
pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
)
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
ColumnEnum.ENTRY_LABEL,
pageOwner.getEntryLabel("SearchDynamicWithImmutableStatus")
)
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY,
ColumnEnum.SEARCH_KEYWORD,
listOf("kw1", "kw2").toString()
)
dynamicRow.checkValue(
QueryEnum.SEARCH_DYNAMIC_ROW_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
)
}
}
private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
Truth.assertThat(getString(query.columnNames.indexOf(column))).isEqualTo(value)
}
private fun Cursor.getExtras(query: QueryEnum, column: ColumnEnum): Bundle? {
val extrasByte = getBlob(query.columnNames.indexOf(column)) ?: return null
val parcel = Parcel.obtain()
parcel.unmarshall(extrasByte, 0, extrasByte.size)
parcel.setDataPosition(0)
val bundle = Bundle()
bundle.readFromParcel(parcel)
return bundle
}
private fun SettingsPage.getEntryId(name: String): String {
return SettingsEntryBuilder.create(this, name).build().id
}
private fun SettingsPage.getEntryLabel(name: String): String {
return SettingsEntryBuilder.create(this, name).build().label
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.slice
import android.content.Context
import android.net.Uri
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import androidx.slice.Slice
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.util.genEntryId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer2
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsSliceDataRepositoryTest {
@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaEnvironment =
SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
private val sliceDataRepository by spaEnvironment.sliceDataRepository
@Test
fun getOrBuildSliceDataTest() {
SpaEnvironmentFactory.reset(spaEnvironment)
// Slice empty
assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
// Slice supported
val page = SppLayer2.createSettingsPage()
val entryId = genEntryId("Layer2Entry1", page)
val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
assertThat(sliceData).isNotNull()
assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
// Slice unsupported
val entryId2 = genEntryId("Layer2Entry2", page)
val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
}
@Test
fun getActiveSliceDataTest() {
SpaEnvironmentFactory.reset(spaEnvironment)
val page = SppLayer2.createSettingsPage()
val entryId = genEntryId("Layer2Entry1", page)
val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
// build slice data first
val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
// slice data is inactive
assertThat(sliceData!!.isActive()).isFalse()
assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
// slice data is active
val observer = Observer<Slice?> { }
sliceData.observeForever(observer)
assertThat(sliceData.isActive()).isTrue()
assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isSameInstanceAs(sliceData)
// slice data is inactive again
sliceData.removeObserver(observer)
assertThat(sliceData.isActive()).isFalse()
assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.slice
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.os.bundleOf
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SliceUtilTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaEnvironment = SpaEnvironmentForTest(context)
@Test
fun sliceUriTest() {
assertThat(Uri.EMPTY.getEntryId()).isNull()
assertThat(Uri.EMPTY.getDestination()).isNull()
assertThat(Uri.EMPTY.getRuntimeArguments().size()).isEqualTo(0)
assertThat(Uri.EMPTY.getSliceId()).isNull()
// valid slice uri
val dest = "myRoute"
val entryId = "myEntry"
val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
val sliceUriWithParams =
Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
assertThat(sliceUriWithParams.getSliceId()).isEqualTo("${entryId}_Bundle[{p1=v1}]")
}
@Test
fun createBroadcastPendingIntentTest() {
SpaEnvironmentFactory.reset(spaEnvironment)
// Empty Slice Uri
assertThat(Uri.EMPTY.createBroadcastPendingIntent()).isNull()
// Valid Slice Uri
val dest = "myRoute"
val entryId = "myEntry"
val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
assertThat(pendingIntent).isNotNull()
assertThat(pendingIntent!!.isBroadcast).isTrue()
assertThat(pendingIntent.isImmutable).isFalse()
}
@Test
fun createBrowsePendingIntentTest() {
SpaEnvironmentFactory.reset(spaEnvironment)
// Empty Slice Uri
assertThat(Uri.EMPTY.createBrowsePendingIntent()).isNull()
// Empty Intent
assertThat(Intent().createBrowsePendingIntent()).isNull()
// Valid Slice Uri
val dest = "myRoute"
val entryId = "myEntry"
val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
val pendingIntent = sliceUri.createBrowsePendingIntent()
assertThat(pendingIntent).isNotNull()
assertThat(pendingIntent!!.isActivity).isTrue()
assertThat(pendingIntent.isImmutable).isTrue()
// Valid Intent
val intent = Intent().apply {
putExtra("spaActivityDestination", dest)
putExtra("highlightEntry", entryId)
}
val pendingIntent2 = intent.createBrowsePendingIntent()
assertThat(pendingIntent2).isNotNull()
assertThat(pendingIntent2!!.isActivity).isTrue()
assertThat(pendingIntent2.isImmutable).isTrue()
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.tests.testutils
import android.os.Bundle
import androidx.navigation.NamedNavArgument
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.util.genPageId
fun createSettingsPage(
sppName: String,
displayName: String? = null,
parameter: List<NamedNavArgument> = emptyList(),
arguments: Bundle? = null
): SettingsPage {
return SettingsPage(
id = genPageId(sppName, parameter, arguments),
sppName = sppName,
displayName = displayName ?: sppName,
parameter = parameter,
arguments = arguments
)
}

View File

@@ -0,0 +1,254 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.tests.testutils
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.android.settingslib.spa.framework.BrowseActivity
import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.EntrySliceData
import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.common.SpaLogger
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
class SpaLoggerForTest : SpaLogger {
data class MsgCountKey(val msg: String, val category: LogCategory)
data class EventCountKey(val id: String, val event: LogEvent, val category: LogCategory)
private val messageCount: MutableMap<MsgCountKey, Int> = mutableMapOf()
private val eventCount: MutableMap<EventCountKey, Int> = mutableMapOf()
override fun message(tag: String, msg: String, category: LogCategory) {
val key = MsgCountKey("[$tag]$msg", category)
messageCount[key] = (messageCount[key] ?: 0) + 1
}
override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
val key = EventCountKey(id, event, category)
eventCount[key] = (eventCount[key] ?: 0) + 1
}
fun getMessageCount(tag: String, msg: String, category: LogCategory): Int {
val key = MsgCountKey("[$tag]$msg", category)
return messageCount[key] ?: 0
}
fun getEventCount(id: String, event: LogEvent, category: LogCategory): Int {
val key = EventCountKey(id, event, category)
return eventCount[key] ?: 0
}
fun reset() {
messageCount.clear()
eventCount.clear()
}
}
class BlankActivity : BrowseActivity()
class BlankSliceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {}
}
object SppHome : SettingsPageProvider {
override val name = "SppHome"
override fun getTitle(arguments: Bundle?): String {
return "TitleHome"
}
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val owner = this.createSettingsPage()
return listOf(
SppLayer1.buildInject().setLink(fromPage = owner).build(),
SppDialog.buildInject().setLink(fromPage = owner).build(),
)
}
}
object SppDisabled : SettingsPageProvider {
override val name = "SppDisabled"
override fun isEnabled(arguments: Bundle?): Boolean = false
override fun getTitle(arguments: Bundle?): String {
return "TitleDisabled"
}
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val owner = this.createSettingsPage()
return listOf(
SppLayer1.buildInject().setLink(fromPage = owner).build(),
)
}
}
object SppLayer1 : SettingsPageProvider {
override val name = "SppLayer1"
override fun getTitle(arguments: Bundle?): String {
return "TitleLayer1"
}
fun buildInject(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(this.createSettingsPage())
.setMacro {
SimplePreferenceMacro(
title = "SppHome to Layer1",
clickRoute = name
)
}
}
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val owner = this.createSettingsPage()
return listOf(
SettingsEntryBuilder.create(owner, "Layer1Entry1").build(),
SppLayer2.buildInject().setLink(fromPage = owner).build(),
SettingsEntryBuilder.create(owner, "Layer1Entry2").build(),
)
}
}
object SppLayer2 : SettingsPageProvider {
override val name = "SppLayer2"
fun buildInject(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(this.createSettingsPage())
}
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val owner = this.createSettingsPage()
return listOf(
SettingsEntryBuilder.create(owner, "Layer2Entry1")
.setSliceDataFn { _, _ ->
return@setSliceDataFn object : EntrySliceData() {
init {
postValue(null)
}
}
}
.build(),
SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
)
}
}
object SppDialog : SettingsPageProvider {
override val name = "SppDialog"
override val navType = SettingsPageProvider.NavType.Dialog
const val CONTENT = "SppDialog Content"
@Composable
override fun Page(arguments: Bundle?) {
Text(CONTENT)
}
fun buildInject() = SettingsEntryBuilder.createInject(this.createSettingsPage())
.setMacro { SimplePreferenceMacro(title = name, clickRoute = name) }
}
object SppForSearch : SettingsPageProvider {
override val name = "SppForSearch"
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val owner = this.createSettingsPage()
return listOf(
SettingsEntryBuilder.create(owner, "SearchStaticWithNoStatus")
.setSearchDataFn { EntrySearchData(title = "SearchStaticWithNoStatus") }
.build(),
SettingsEntryBuilder.create(owner, "SearchStaticWithMutableStatus")
.setHasMutableStatus(true)
.setSearchDataFn { EntrySearchData(title = "SearchStaticWithMutableStatus") }
.setStatusDataFn { EntryStatusData(isSwitchOff = true) }
.build(),
SettingsEntryBuilder.create(owner, "SearchDynamicWithMutableStatus")
.setIsSearchDataDynamic(true)
.setHasMutableStatus(true)
.setSearchDataFn { EntrySearchData(title = "SearchDynamicWithMutableStatus") }
.setStatusDataFn { EntryStatusData(isDisabled = true) }
.build(),
SettingsEntryBuilder.create(owner, "SearchDynamicWithImmutableStatus")
.setIsSearchDataDynamic(true)
.setSearchDataFn {
EntrySearchData(
title = "SearchDynamicWithImmutableStatus",
keyword = listOf("kw1", "kw2")
)
}
.setStatusDataFn { EntryStatusData(isDisabled = true) }
.build(),
)
}
}
class SpaEnvironmentForTest(
context: Context,
rootPages: List<SettingsPage> = emptyList(),
override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
BlankSliceBroadcastReceiver::class.java,
override val logger: SpaLogger = object : SpaLogger {}
) : SpaEnvironment(context) {
override val pageProviderRepository = lazy {
SettingsPageProviderRepository(
listOf(
SppHome, SppLayer1, SppLayer2,
SppForSearch, SppDisabled,
object : SettingsPageProvider {
override val name = "SppWithParam"
override val parameter = listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
)
},
object : SettingsPageProvider {
override val name = "SppWithRtParam"
override val parameter = listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
navArgument("rt_param") { type = NavType.StringType },
)
},
SppDialog,
),
rootPages
)
}
fun createPage(sppName: String, arguments: Bundle? = null): SettingsPage {
return pageProviderRepository.value
.getProviderOrNull(sppName)!!.createSettingsPage(arguments)
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.button
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.material.icons.outlined.Close
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.getBoundsInRoot
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ActionButtonsTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun button_displayed() {
composeTestRule.setContent {
ActionButtons(
listOf(
ActionButton(
text = "Open",
imageVector = Icons.AutoMirrored.Outlined.Launch
) {},
)
)
}
composeTestRule.onNodeWithText("Open").assertIsDisplayed()
}
@Test
fun button_clickable() {
var clicked by mutableStateOf(false)
composeTestRule.setContent {
ActionButtons(
listOf(
ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {
clicked = true
},
)
)
}
composeTestRule.onNodeWithText("Open").performClick()
assertThat(clicked).isTrue()
}
@Test
fun twoButtons_positionIsAligned() {
composeTestRule.setContent {
ActionButtons(
listOf(
ActionButton(
text = "Open",
imageVector = Icons.AutoMirrored.Outlined.Launch
) {},
ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {},
)
)
}
assertThat(composeTestRule.onNodeWithText("Open").getBoundsInRoot().top)
.isEqualTo(composeTestRule.onNodeWithText("Close").getBoundsInRoot().top)
}
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.card
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.isNotDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsCardTest {
@get:Rule val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun settingsCard_titleDisplayed() {
composeTestRule.setContent {
SettingsCard(
CardModel(
title = TITLE,
text = "",
)
)
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun settingsCard_textDisplayed() {
composeTestRule.setContent {
SettingsCard(
CardModel(
title = "",
text = TEXT,
)
)
}
composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
}
@Test
fun settingsCard_buttonDisplayed() {
composeTestRule.setContent {
SettingsCard(
CardModel(
title = "",
text = "",
buttons = listOf(CardButton(text = TEXT) {}),
)
)
}
composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
}
@Test
fun settingsCard_buttonCanBeClicked() {
var buttonClicked = false
composeTestRule.setContent {
SettingsCard(
CardModel(
title = "",
text = "",
buttons = listOf(CardButton(text = TEXT) { buttonClicked = true }),
)
)
}
composeTestRule.onNodeWithText(TEXT).performClick()
assertThat(buttonClicked).isTrue()
}
@Test
fun settingsCard_buttonHaveContentDescription() {
composeTestRule.setContent {
SettingsCard(
CardModel(
title = "",
text = "",
buttons = listOf(CardButton(
text = TEXT,
contentDescription = CONTENT_DESCRIPTION,
) {}
),
)
)
}
composeTestRule.onNodeWithContentDescription(CONTENT_DESCRIPTION).assertIsDisplayed()
}
@Test
fun settingsCard_dismiss() {
composeTestRule.setContent {
var isVisible by remember { mutableStateOf(true) }
SettingsCard(
CardModel(
title = TITLE,
text = "",
isVisible = { isVisible },
onDismiss = { isVisible = false },
)
)
}
composeTestRule.onNodeWithContentDescription(
context.getString(androidx.compose.material3.R.string.m3c_snackbar_dismiss)
).performClick()
composeTestRule.onNodeWithText(TEXT).isNotDisplayed()
}
@Test
fun settingsCard_clickable() {
var clicked by mutableStateOf(false)
composeTestRule.setContent {
SettingsCard(
CardModel(
title = TITLE,
text = "",
) { clicked = true }
)
}
composeTestRule.onNodeWithText(TITLE).performClick()
assertThat(clicked).isTrue()
}
private companion object {
const val TITLE = "Title"
const val TEXT = "Text"
const val CONTENT_DESCRIPTION = "content-description"
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.card
import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Error
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.isNotDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsCollapsibleCardTest {
@get:Rule
val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun settingsCollapsibleCard_titleDisplayed() {
setContent()
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun settingsCollapsibleCard_cardCountDisplayed() {
setContent()
composeTestRule.onNodeWithText("1").assertIsDisplayed()
}
@Test
fun settingsCollapsibleCard_initial_cardTextNotExists() {
setContent()
composeTestRule.onNodeWithText(CARD_TEXT).assertDoesNotExist()
}
@Test
fun settingsCollapsibleCard_afterExpand_cardTextDisplayed() {
setContent()
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onNodeWithText(CARD_TEXT).assertIsDisplayed()
}
@Test
fun settingsCollapsibleCard_dismiss() {
setContent()
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onNodeWithContentDescription(
context.getString(androidx.compose.material3.R.string.m3c_snackbar_dismiss)
).performClick()
composeTestRule.onNodeWithText(CARD_TEXT).isNotDisplayed()
composeTestRule.onNodeWithText("0").assertIsDisplayed()
}
private fun setContent() {
composeTestRule.setContent {
var isVisible by rememberSaveable { mutableStateOf(true) }
SettingsCollapsibleCard(
title = TITLE,
imageVector = Icons.Outlined.Error,
models = listOf(
CardModel(
title = "",
text = CARD_TEXT,
isVisible = { isVisible },
onDismiss = { isVisible = false },
)
),
)
}
}
private companion object {
const val TITLE = "Title"
const val CARD_TEXT = "Card Text"
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.chart
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.assertContainsColor
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ChartTest {
@get:Rule
val composeTestRule = createComposeRule()
private val chart = SemanticsPropertyKey<String>("Chart")
private var SemanticsPropertyReceiver.chart by chart
private fun hasChart(chart: String): SemanticsMatcher =
SemanticsMatcher.expectValue(this.chart, chart)
@Test
fun line_chart_displayed() {
composeTestRule.setContent {
LineChart(
chartDataList = listOf(
LineChartData(x = 0f, y = 0f),
LineChartData(x = 1f, y = 0.1f),
LineChartData(x = 2f, y = 0.2f),
LineChartData(x = 3f, y = 0.7f),
LineChartData(x = 4f, y = 0.9f),
LineChartData(x = 5f, y = 1.0f),
LineChartData(x = 6f, y = 0.8f),
),
modifier = Modifier.semantics { chart = "line" }
)
}
composeTestRule.onNode(hasChart("line")).assertIsDisplayed()
}
@Test
fun bar_chart_displayed() {
composeTestRule.setContent {
BarChart(object : BarChartModel {
override val chartDataList = listOf(
BarChartData(x = 0f, y = listOf(12f)),
BarChartData(x = 1f, y = listOf(5f)),
BarChartData(x = 2f, y = listOf(21f)),
BarChartData(x = 3f, y = listOf(5f)),
BarChartData(x = 4f, y = listOf(10f)),
BarChartData(x = 5f, y = listOf(9f)),
BarChartData(x = 6f, y = listOf(1f)),
)
override val colors = listOf(Color.Blue)
})
}
composeTestRule.onRoot().captureToImage().assertContainsColor(Color.Blue)
}
@Test
fun pie_chart_displayed() {
composeTestRule.setContent {
PieChart(
chartDataList = listOf(
PieChartData(label = "Settings", value = 20f),
PieChartData(label = "Chrome", value = 5f),
PieChartData(label = "Gmail", value = 3f),
),
centerText = "Today",
modifier = Modifier.semantics { chart = "pie" }
)
}
composeTestRule.onNode(hasChart("pie")).assertIsDisplayed()
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.dialog
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.onDialogText
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsAlertDialogTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_whenDialogNotOpen_notDisplayed() {
composeTestRule.setContent {
rememberAlertDialogPresenter(title = TITLE)
}
composeTestRule.onDialogText(TITLE).assertDoesNotExist()
}
@Test
fun title_displayed() {
setAndOpenDialog {
rememberAlertDialogPresenter(title = TITLE)
}
composeTestRule.onDialogText(TITLE).assertIsDisplayed()
}
@Test
fun text_displayed() {
setAndOpenDialog {
rememberAlertDialogPresenter(text = { Text(TEXT) })
}
composeTestRule.onDialogText(TEXT).assertIsDisplayed()
}
@Test
fun confirmButton_displayed() {
setAndOpenDialog {
rememberAlertDialogPresenter(confirmButton = AlertDialogButton(CONFIRM_TEXT))
}
composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed()
}
@Test
fun confirmButton_clickable() {
var confirmButtonClicked = false
setAndOpenDialog {
rememberAlertDialogPresenter(confirmButton = AlertDialogButton(CONFIRM_TEXT) {
confirmButtonClicked = true
})
}
composeTestRule.onDialogText(CONFIRM_TEXT).performClick()
assertThat(confirmButtonClicked).isTrue()
}
@Test
fun dismissButton_displayed() {
setAndOpenDialog {
rememberAlertDialogPresenter(dismissButton = AlertDialogButton(DISMISS_TEXT))
}
composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed()
}
@Test
fun dismissButton_clickable() {
var dismissButtonClicked = false
setAndOpenDialog {
rememberAlertDialogPresenter(dismissButton = AlertDialogButton(DISMISS_TEXT) {
dismissButtonClicked = true
})
}
composeTestRule.onDialogText(DISMISS_TEXT).performClick()
assertThat(dismissButtonClicked).isTrue()
}
private fun setAndOpenDialog(dialog: @Composable () -> AlertDialogPresenter) {
composeTestRule.setContent {
val dialogPresenter = dialog()
LaunchedEffect(Unit) {
dialogPresenter.open()
}
}
}
private companion object {
const val CONFIRM_TEXT = "Confirm"
const val DISMISS_TEXT = "Dismiss"
const val TITLE = "Title"
const val TEXT = "Text"
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.dialog
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.onDialogText
import com.android.settingslib.spa.widget.ui.SettingsDialogItem
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsDialogTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
SettingsDialog(title = TITLE, onDismissRequest = {}) {}
}
composeTestRule.onDialogText(TITLE).assertIsDisplayed()
}
@Test
fun text_displayed() {
composeTestRule.setContent {
SettingsDialog(title = "", onDismissRequest = {}) {
SettingsDialogItem(text = TEXT)
}
}
composeTestRule.onDialogText(TEXT).assertIsDisplayed()
}
private companion object {
const val TITLE = "Title"
const val TEXT = "Text"
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.editor
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsDropdownBoxTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun dropdownMenuBox_displayed() {
composeTestRule.setContent {
var selectedItem by remember { mutableStateOf(0) }
SettingsDropdownBox(
label = LABEL,
options = options,
selectedOptionIndex = selectedItem,
) { selectedItem = it }
}
composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
}
@Test
fun dropdownMenuBox_enabled_expanded() {
composeTestRule.setContent {
var selectedItem by remember { mutableIntStateOf(0) }
SettingsDropdownBox(
label = LABEL,
options = options,
selectedOptionIndex = selectedItem
) { selectedItem = it }
}
composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
composeTestRule.onNodeWithText(LABEL).performClick()
composeTestRule.onNodeWithText(ITEM2).assertIsDisplayed()
}
@Test
fun dropdownMenuBox_notEnabled_notExpanded() {
composeTestRule.setContent {
var selectedItem by remember { mutableIntStateOf(0) }
SettingsDropdownBox(
label = LABEL,
options = options,
enabled = false,
selectedOptionIndex = selectedItem
) { selectedItem = it }
}
composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
composeTestRule.onNodeWithText(LABEL).performClick()
composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
}
@Test
fun dropdownMenuBox_valueChanged() {
composeTestRule.setContent {
var selectedItem by remember { mutableIntStateOf(0) }
SettingsDropdownBox(
label = LABEL,
options = options,
selectedOptionIndex = selectedItem
) { selectedItem = it }
}
composeTestRule.onNodeWithText(ITEM2).assertDoesNotExist()
composeTestRule.onNodeWithText(LABEL).performClick()
composeTestRule.onNodeWithText(ITEM2).performClick()
composeTestRule.onNodeWithText(ITEM2).assertIsDisplayed()
}
private companion object {
const val LABEL = "Label"
const val ITEM2 = "item2"
val options = listOf("item1", ITEM2, "item3")
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.editor
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isPopup
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.hasRole
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsDropdownCheckBoxTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun dropdownCheckBox_displayed() {
val item1 = SettingsDropdownCheckOption("item1")
val item2 = SettingsDropdownCheckOption("item2")
val item3 = SettingsDropdownCheckOption("item3")
composeTestRule.setContent {
SettingsDropdownCheckBox(
label = LABEL,
options = listOf(item1, item2, item3),
)
}
composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
}
@Test
fun dropdownCheckBox_expanded() {
val item1 = SettingsDropdownCheckOption("item1")
val item2 = SettingsDropdownCheckOption("item2")
val item3 = SettingsDropdownCheckOption("item3")
composeTestRule.setContent {
SettingsDropdownCheckBox(
label = LABEL,
options = listOf(item1, item2, item3),
)
}
composeTestRule.onOption(item3).assertDoesNotExist()
composeTestRule.onNodeWithText(LABEL).performClick()
composeTestRule.onOption(item3).assertIsDisplayed()
}
@Test
fun dropdownCheckBox_valueAdded() {
val item1 = SettingsDropdownCheckOption("item1")
val item2 = SettingsDropdownCheckOption("item2")
val item3 = SettingsDropdownCheckOption("item3")
composeTestRule.setContent {
SettingsDropdownCheckBox(
label = LABEL,
options = listOf(item1, item2, item3),
)
}
composeTestRule.onDropdownBox(item3.text).assertDoesNotExist()
composeTestRule.onNodeWithText(LABEL).performClick()
composeTestRule.onOption(item3).performClick()
composeTestRule.onDropdownBox(item3.text).assertIsDisplayed()
assertThat(item3.selected.value).isTrue()
}
@Test
fun dropdownCheckBox_valueDeleted() {
val item1 = SettingsDropdownCheckOption("item1")
val item2 = SettingsDropdownCheckOption("item2", selected = mutableStateOf(true))
val item3 = SettingsDropdownCheckOption("item3")
composeTestRule.setContent {
SettingsDropdownCheckBox(
label = LABEL,
options = listOf(item1, item2, item3),
)
}
composeTestRule.onDropdownBox(item2.text).assertIsDisplayed()
composeTestRule.onNodeWithText(LABEL).performClick()
composeTestRule.onOption(item2).performClick()
composeTestRule.onDropdownBox(item2.text).assertDoesNotExist()
assertThat(item2.selected.value).isFalse()
}
@Test
fun dropdownCheckBox_withSelectAll() {
val selectAll = SettingsDropdownCheckOption("All", isSelectAll = true)
val item1 = SettingsDropdownCheckOption("item1")
val item2 = SettingsDropdownCheckOption("item2")
composeTestRule.setContent {
SettingsDropdownCheckBox(
label = LABEL,
options = listOf(selectAll, item1, item2),
)
}
composeTestRule.onNodeWithText(LABEL).performClick()
composeTestRule.onOption(selectAll).performClick()
composeTestRule.onDropdownBox(selectAll.text).assertIsDisplayed()
composeTestRule.onDropdownBox(item1.text).assertDoesNotExist()
composeTestRule.onDropdownBox(item2.text).assertDoesNotExist()
assertThat(item1.selected.value).isTrue()
assertThat(item2.selected.value).isTrue()
}
private companion object {
const val LABEL = "Label"
}
}
private fun ComposeContentTestRule.onDropdownBox(text: String) =
onNode(hasRole(Role.DropdownList) and hasText(text))
private fun ComposeContentTestRule.onOption(option: SettingsDropdownCheckOption) =
onNode(hasAnyAncestor(isPopup()) and hasText(option.text))

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.editor
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertTextContains
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextReplacement
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsOutlinedTextFieldsTest {
@get:Rule
val composeTestRule = createComposeRule()
private val outlinedTextFieldLabel = "OutlinedTextField Enabled"
private val enabledValue = "Enabled Value"
private val disabledValue = "Disabled Value"
private val valueChanged = "Value Changed"
@Test
fun outlinedTextField_displayed() {
composeTestRule.setContent {
SettingsOutlinedTextField(
value = enabledValue,
label = outlinedTextFieldLabel,
enabled = true,
onTextChange = {})
}
composeTestRule.onNodeWithText(outlinedTextFieldLabel, substring = true)
.assertIsDisplayed()
}
@Test
fun outlinedTextFields_enabled() {
composeTestRule.setContent {
SettingsOutlinedTextField(
value = enabledValue,
label = outlinedTextFieldLabel,
enabled = true,
onTextChange = {})
}
composeTestRule.onNodeWithText(outlinedTextFieldLabel, substring = true)
.assertIsEnabled()
}
@Test
fun outlinedTextFields_disabled() {
composeTestRule.setContent {
SettingsOutlinedTextField(
value = disabledValue,
label = outlinedTextFieldLabel,
enabled = false,
onTextChange = {})
}
composeTestRule.onNodeWithText(outlinedTextFieldLabel, substring = true)
.assertIsNotEnabled()
}
@Test
fun outlinedTextFields_inputValue() {
composeTestRule.setContent {
var value by remember { mutableStateOf(enabledValue) }
SettingsOutlinedTextField(
value = value,
label = outlinedTextFieldLabel,
enabled = true,
onTextChange = { value = it })
}
composeTestRule.onNodeWithText(outlinedTextFieldLabel, substring = true)
.performTextReplacement(valueChanged)
composeTestRule.onNodeWithText(outlinedTextFieldLabel, substring = true)
.assertTextContains(valueChanged)
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.editor
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertTextContains
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextReplacement
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsTextFieldPasswordTest {
@get:Rule
val composeTestRule = createComposeRule()
private val label = "label"
private val value = "value"
private val valueChanged = "Value Changed"
private val visibilityIconTag = "Visibility Icon"
@Test
fun textFieldPassword_displayed() {
composeTestRule.setContent {
SettingsTextFieldPassword(
value = value,
label = label,
onTextChange = {})
}
composeTestRule.onNodeWithText(label, substring = true)
.assertIsDisplayed()
}
@Test
fun textFieldPassword_invisible() {
composeTestRule.setContent {
var value by remember { mutableStateOf(value) }
SettingsTextFieldPassword(
value = value,
label = label,
onTextChange = { value = it })
}
composeTestRule.onNodeWithText(value, substring = true)
.assertDoesNotExist()
}
@Test
fun textFieldPassword_visible_inputValue() {
composeTestRule.setContent {
var value by remember { mutableStateOf(value) }
SettingsTextFieldPassword(
value = value,
label = label,
onTextChange = { value = it })
}
composeTestRule.onNodeWithTag(visibilityIconTag)
.performClick()
composeTestRule.onNodeWithText(label, substring = true)
.performTextReplacement(valueChanged)
composeTestRule.onNodeWithText(label, substring = true)
.assertTextContains(valueChanged)
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.illustration
import androidx.annotation.DrawableRes
import androidx.annotation.RawRes
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.filterToOne
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.test.R
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class IllustrationTest {
@get:Rule
val composeTestRule = createComposeRule()
private val drawableId = SemanticsPropertyKey<Int>("DrawableResId")
private var SemanticsPropertyReceiver.drawableId by drawableId
@Test
fun image_displayed() {
val resId = R.drawable.accessibility_captioning_banner
composeTestRule.setContent {
Illustration(
resId = resId,
resourceType = ResourceType.IMAGE,
modifier = Modifier.semantics { drawableId = resId }
)
}
fun hasDrawable(@DrawableRes id: Int): SemanticsMatcher =
SemanticsMatcher.expectValue(drawableId, id)
val isIllustrationNode = hasAnyAncestor(hasDrawable(resId))
composeTestRule.onAllNodes(hasDrawable(resId))
.filterToOne(isIllustrationNode)
.assertIsDisplayed()
}
private val rawId = SemanticsPropertyKey<Int>("RawResId")
private var SemanticsPropertyReceiver.rawId by rawId
@Test
fun empty_lottie_not_displayed() {
val resId = R.raw.empty
composeTestRule.setContent {
Illustration(
resId = resId,
resourceType = ResourceType.LOTTIE,
modifier = Modifier.semantics { rawId = resId }
)
}
fun hasRaw(@RawRes id: Int): SemanticsMatcher =
SemanticsMatcher.expectValue(rawId, id)
val isIllustrationNode = hasAnyAncestor(hasRaw(resId))
composeTestRule.onAllNodes(hasRaw(resId))
.filterToOne(isIllustrationNode)
.assertIsNotDisplayed()
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.isToggleable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CheckboxPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
testCheckboxPreference(changeable = true)
}
composeTestRule.onNodeWithText("CheckboxPreference").assertIsDisplayed()
}
@Test
fun toggleable_initialStateIsCorrect() {
composeTestRule.setContent {
testCheckboxPreference(changeable = true)
}
composeTestRule.onNode(isToggleable()).assertIsOff()
}
@Test
fun click_changeable_withEffect() {
composeTestRule.setContent {
testCheckboxPreference(changeable = true)
}
composeTestRule.onNodeWithText("CheckboxPreference").performClick()
composeTestRule.onNode(isToggleable()).assertIsOn()
}
@Test
fun click_notChangeable_noEffect() {
composeTestRule.setContent {
testCheckboxPreference(changeable = false)
}
composeTestRule.onNodeWithText("CheckboxPreference").performClick()
composeTestRule.onNode(isToggleable()).assertIsOff()
}
}
@Composable
private fun testCheckboxPreference(changeable: Boolean) {
var checked by rememberSaveable { mutableStateOf(false) }
CheckboxPreference(remember {
object : CheckboxPreferenceModel {
override val title = "CheckboxPreference"
override val checked = { checked }
override val changeable = { changeable }
override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
}
})
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.onDialogText
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ListPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
override val options = emptyList<ListPreferenceOption>()
override val selectedId = mutableIntStateOf(0)
override val onIdSelected: (Int) -> Unit = {}
}
})
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun summary_showSelectedText() {
composeTestRule.setContent {
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
override val selectedId = mutableIntStateOf(1)
override val onIdSelected: (Int) -> Unit = {}
}
})
}
composeTestRule.onNodeWithText("A").assertIsDisplayed()
}
@Test
fun click_optionsIsEmpty_notShowDialog() {
composeTestRule.setContent {
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
override val options = emptyList<ListPreferenceOption>()
override val selectedId = mutableIntStateOf(0)
override val onIdSelected: (Int) -> Unit = {}
}
})
}
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onDialogText(TITLE).assertDoesNotExist()
}
@Test
fun click_notEnabled_notShowDialog() {
composeTestRule.setContent {
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
override val enabled = { false }
override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
override val selectedId = mutableIntStateOf(1)
override val onIdSelected: (Int) -> Unit = {}
}
})
}
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onDialogText(TITLE).assertDoesNotExist()
}
@Test
fun click_optionsNotEmpty_showDialog() {
composeTestRule.setContent {
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
override val selectedId = mutableIntStateOf(1)
override val onIdSelected: (Int) -> Unit = {}
}
})
}
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onDialogText(TITLE).assertIsDisplayed()
}
@Test
fun click_optionsNotEmptyAndItemHasSummary_itemShowSummary() {
composeTestRule.setContent {
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
override val options =
listOf(ListPreferenceOption(id = 1, text = "A", summary = "A_Summary"))
override val selectedId = mutableIntStateOf(1)
override val onIdSelected: (Int) -> Unit = {}
}
})
}
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onDialogText(TITLE).assertIsDisplayed()
composeTestRule.onNodeWithText("A_Summary").assertIsDisplayed()
}
@Test
fun select() {
val selectedId = mutableIntStateOf(1)
composeTestRule.setContent {
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
override val options = listOf(
ListPreferenceOption(id = 1, text = "A"),
ListPreferenceOption(id = 2, text = "B"),
)
override val selectedId = selectedId
override val onIdSelected = { id: Int -> selectedId.intValue = id }
}
})
}
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onDialogText("B").performClick()
composeTestRule.onNodeWithText("B").assertIsDisplayed()
}
@Test
fun select_dialogOpenThenDisable_itemAlsoDisabled() {
val selectedId = mutableIntStateOf(1)
val enabledState = mutableStateOf(true)
composeTestRule.setContent {
ListPreference(remember {
object : ListPreferenceModel {
override val title = TITLE
override val enabled = { enabledState.value }
override val options = listOf(
ListPreferenceOption(id = 1, text = "A"),
ListPreferenceOption(id = 2, text = "B"),
)
override val selectedId = selectedId
override val onIdSelected = { id: Int -> selectedId.intValue = id }
}
})
}
composeTestRule.onNodeWithText(TITLE).performClick()
enabledState.value = false
composeTestRule.onDialogText("B").assertIsDisplayed().assertIsNotEnabled()
}
private companion object {
const val TITLE = "Title"
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.isToggleable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
private const val TITLE = "Title"
@RunWith(AndroidJUnit4::class)
class MainSwitchPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
TestMainSwitchPreference(changeable = true)
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun toggleable_initialStateIsCorrect() {
composeTestRule.setContent {
TestMainSwitchPreference(changeable = true)
}
composeTestRule.onNode(isToggleable()).assertIsOff()
}
@Test
fun click_changeable_withEffect() {
composeTestRule.setContent {
TestMainSwitchPreference(changeable = true)
}
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onNode(isToggleable()).assertIsOn()
}
@Test
fun click_notChangeable_noEffect() {
composeTestRule.setContent {
TestMainSwitchPreference(changeable = false)
}
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onNode(isToggleable()).assertIsOff()
}
}
@Composable
private fun TestMainSwitchPreference(changeable: Boolean) {
var checked by rememberSaveable { mutableStateOf(false) }
MainSwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = TITLE
override val checked = { checked }
override val changeable = { changeable }
override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
}
})
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.assertHeightIsAtLeast
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
Preference(object : PreferenceModel {
override val title = TITLE
})
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun longSummary_notSingleLine_atLeastTwoLinesHeight() {
var lineHeightDp: Dp = Dp.Unspecified
composeTestRule.setContent {
Box(Modifier.width(BOX_WIDTH)) {
Preference(object : PreferenceModel {
override val title = TITLE
override val summary = { LONG_SUMMARY }
})
}
lineHeightDp = with(LocalDensity.current) {
MaterialTheme.typography.bodyMedium.lineHeight.toDp()
}
}
composeTestRule.onNodeWithText(LONG_SUMMARY).assertHeightIsAtLeast(lineHeightDp.times(2))
}
@Test
fun longSummary_notSingleLine_onlyOneLineHeight() {
var lineHeightDp: Dp = Dp.Unspecified
composeTestRule.setContent {
Box(Modifier.width(BOX_WIDTH)) {
Preference(
model = object : PreferenceModel {
override val title = TITLE
override val summary = { LONG_SUMMARY }
},
singleLineSummary = true,
)
}
lineHeightDp = with(LocalDensity.current) {
MaterialTheme.typography.bodyMedium.lineHeight.toDp()
}
}
val summaryNode = composeTestRule.onNodeWithText(LONG_SUMMARY)
try {
// There is no assertHeightIsAtMost, so use the assertHeightIsAtLeast and catch the
// expected exception.
summaryNode.assertHeightIsAtLeast(lineHeightDp.times(2))
} catch (e: AssertionError) {
assertThat(e).hasMessageThat().contains("height")
return
}
fail("Expect AssertionError")
}
@Test
fun click_enabled_withEffect() {
composeTestRule.setContent {
var count by remember { mutableStateOf(0) }
Preference(object : PreferenceModel {
override val title = TITLE
override val summary = { count.toString() }
override val onClick: (() -> Unit) = { count++ }
})
}
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onNodeWithText("1").assertIsDisplayed()
}
@Test
fun click_disabled_noEffect() {
composeTestRule.setContent {
var count by remember { mutableStateOf(0) }
Preference(object : PreferenceModel {
override val title = TITLE
override val summary = { count.toString() }
override val enabled = { false }
override val onClick: (() -> Unit) = { count++ }
})
}
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onNodeWithText("0").assertIsDisplayed()
}
companion object {
private const val TITLE = "Title"
private const val LONG_SUMMARY =
"Long long long long long long long long long long long long long long long summary"
private val BOX_WIDTH = 100.dp
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.SemanticsProperties.ProgressBarRangeInfo
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ProgressBarPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
ProgressBarPreference(object : ProgressBarPreferenceModel {
override val title = "Title"
override val progress = 0.2f
})
}
composeTestRule.onNodeWithText("Title").assertIsDisplayed()
}
@Test
fun data_displayed() {
composeTestRule.setContent {
ProgressBarWithDataPreference(
model = object : ProgressBarPreferenceModel {
override val title = "Title"
override val progress = 0.2f
override val icon = Icons.AutoMirrored.Outlined.Launch
},
data = "Data",
)
}
composeTestRule.onNodeWithText("Title").assertIsDisplayed()
composeTestRule.onNodeWithText("Data").assertIsDisplayed()
}
@Test
fun progressBar_displayed() {
composeTestRule.setContent {
ProgressBarPreference(object : ProgressBarPreferenceModel {
override val title = "Title"
override val progress = 0.2f
})
}
fun progressEqualsTo(progress: Float): SemanticsMatcher =
SemanticsMatcher.expectValue(
ProgressBarRangeInfo,
ProgressBarRangeInfo(progress, 0f..1f, 0)
)
composeTestRule.onNode(progressEqualsTo(0.2f)).assertIsDisplayed()
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsSelectable
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RadioPreferencesTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
RadioPreferences(remember {
object : ListPreferenceModel {
override val title = TITLE
override val options = emptyList<ListPreferenceOption>()
override val selectedId = mutableIntStateOf(0)
override val onIdSelected: (Int) -> Unit = {}
}
})
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun item_displayed() {
val selectedId = mutableIntStateOf(1)
composeTestRule.setContent {
RadioPreferences(remember {
object : ListPreferenceModel {
override val title = TITLE
override val options = listOf(
ListPreferenceOption(id = 1, text = "A"),
ListPreferenceOption(id = 2, text = "B"),
)
override val selectedId = selectedId
override val onIdSelected = { id: Int -> selectedId.intValue = id }
}
})
}
composeTestRule.onNodeWithText("A").assertIsDisplayed()
composeTestRule.onNodeWithText("B").assertIsDisplayed()
}
@Test
fun item_selectable() {
val selectedId = mutableIntStateOf(1)
val enabledState = mutableStateOf(true)
composeTestRule.setContent {
RadioPreferences(remember {
object : ListPreferenceModel {
override val title = TITLE
override val enabled = { enabledState.value }
override val options = listOf(
ListPreferenceOption(id = 1, text = "A"),
ListPreferenceOption(id = 2, text = "B"),
)
override val selectedId = selectedId
override val onIdSelected = { id: Int -> selectedId.intValue = id }
}
})
}
composeTestRule.onNodeWithText("A").assertIsSelectable()
composeTestRule.onNodeWithText("B").assertIsSelectable()
}
@Test
fun item_single_selected() {
val selectedId = mutableIntStateOf(1)
val enabledState = mutableStateOf(true)
composeTestRule.setContent {
RadioPreferences(remember {
object : ListPreferenceModel {
override val title = TITLE
override val enabled = { enabledState.value }
override val options = listOf(
ListPreferenceOption(id = 1, text = "A"),
ListPreferenceOption(id = 2, text = "B"),
)
override val selectedId = selectedId
override val onIdSelected = { id: Int -> selectedId.intValue = id }
}
})
}
composeTestRule.onNodeWithText("B").assertIsSelectable()
composeTestRule.onNodeWithText("B").performClick()
composeTestRule.onNodeWithText("B").assertIsSelected()
composeTestRule.onNodeWithText("A").assertIsNotSelected()
composeTestRule.onNodeWithText("A").performClick()
composeTestRule.onNodeWithText("A").assertIsSelected()
composeTestRule.onNodeWithText("B").assertIsNotSelected()
}
@Test
fun select_itemDisabled() {
val selectedId = mutableIntStateOf(1)
val enabledState = mutableStateOf(true)
composeTestRule.setContent {
RadioPreferences(remember {
object : ListPreferenceModel {
override val title = TITLE
override val enabled = { enabledState.value }
override val options = listOf(
ListPreferenceOption(id = 1, text = "A"),
ListPreferenceOption(id = 2, text = "B"),
)
override val selectedId = selectedId
override val onIdSelected = { id: Int -> selectedId.intValue = id }
}
})
}
enabledState.value = false
composeTestRule.onNodeWithText("A").assertIsDisplayed().assertIsNotEnabled()
composeTestRule.onNodeWithText("B").assertIsDisplayed().assertIsNotEnabled()
}
private companion object {
const val TITLE = "Title"
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.SemanticsProperties.ProgressBarRangeInfo
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SliderPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
SliderPreference(object : SliderPreferenceModel {
override val title = "Slider"
override val initValue = 40
})
}
composeTestRule.onNodeWithText("Slider").assertIsDisplayed()
}
@Test
fun slider_displayed() {
composeTestRule.setContent {
SliderPreference(object : SliderPreferenceModel {
override val title = "Slider"
override val initValue = 40
})
}
fun progressEqualsTo(progress: Float): SemanticsMatcher =
SemanticsMatcher.expectValue(
ProgressBarRangeInfo,
ProgressBarRangeInfo(progress, 0f..100f, 0)
)
composeTestRule.onNode(progressEqualsTo(40f)).assertIsDisplayed()
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.isToggleable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SwitchPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
TestSwitchPreference(changeable = true)
}
composeTestRule.onNodeWithText("SwitchPreference").assertIsDisplayed()
}
@Test
fun toggleable_initialStateIsCorrect() {
composeTestRule.setContent {
TestSwitchPreference(changeable = true)
}
composeTestRule.onNode(isToggleable()).assertIsOff()
}
@Test
fun click_changeable_withEffect() {
composeTestRule.setContent {
TestSwitchPreference(changeable = true)
}
composeTestRule.onNodeWithText("SwitchPreference").performClick()
composeTestRule.onNode(isToggleable()).assertIsOn()
}
@Test
fun click_notChangeable_noEffect() {
composeTestRule.setContent {
TestSwitchPreference(changeable = false)
}
composeTestRule.onNodeWithText("SwitchPreference").performClick()
composeTestRule.onNode(isToggleable()).assertIsOff()
}
}
@Composable
private fun TestSwitchPreference(changeable: Boolean) {
var checked by rememberSaveable { mutableStateOf(false) }
SwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = "SwitchPreference"
override val checked = { checked }
override val changeable = { changeable }
override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
}
})
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
private const val TEST_MODEL_TITLE = "TwoTargetButtonPreference"
private const val TEST_MODEL_SUMMARY = "TestSummary"
private const val TEST_BUTTON_ICON_DESCRIPTION = "TestButtonIconDescription"
private val TEST_BUTTON_ICON = Icons.Outlined.Delete
@RunWith(AndroidJUnit4::class)
class TwoTargetButtonPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
testTwoTargetButtonPreference()
}
composeTestRule.onNodeWithText(TEST_MODEL_TITLE).assertIsDisplayed()
}
@Test
fun clickable_label_canBeClicked() {
var clicked = false
composeTestRule.setContent {
testTwoTargetButtonPreference(onClick = { clicked = true })
}
composeTestRule.onNodeWithText(TEST_MODEL_TITLE).performClick()
Truth.assertThat(clicked).isTrue()
}
@Test
fun clickable_button_label_canBeClicked() {
var clicked = false
composeTestRule.setContent {
testTwoTargetButtonPreference(onButtonClick = { clicked = true })
}
composeTestRule.onNodeWithContentDescription(TEST_BUTTON_ICON_DESCRIPTION).performClick()
Truth.assertThat(clicked).isTrue()
}
}
@Composable
private fun testTwoTargetButtonPreference(
onClick: () -> Unit = {},
onButtonClick: () -> Unit = {},
) {
TwoTargetButtonPreference(
title = TEST_MODEL_TITLE,
summary = { TEST_MODEL_SUMMARY },
onClick = onClick,
buttonIcon = TEST_BUTTON_ICON,
buttonIconDescription = TEST_BUTTON_ICON_DESCRIPTION,
onButtonClick = onButtonClick
)
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.preference
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.isToggleable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TwoTargetSwitchPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_displayed() {
composeTestRule.setContent {
TestTwoTargetSwitchPreference(changeable = true)
}
composeTestRule.onNodeWithText("TwoTargetSwitchPreference").assertIsDisplayed()
}
@Test
fun toggleable_initialStateIsCorrect() {
composeTestRule.setContent {
TestTwoTargetSwitchPreference(changeable = true)
}
composeTestRule.onNode(isToggleable()).assertIsOff()
}
@Test
fun toggleable_changeable_withEffect() {
composeTestRule.setContent {
TestTwoTargetSwitchPreference(changeable = true)
}
composeTestRule.onNode(isToggleable()).performClick()
composeTestRule.onNode(isToggleable()).assertIsOn()
}
@Test
fun toggleable_notChangeable_noEffect() {
composeTestRule.setContent {
TestTwoTargetSwitchPreference(changeable = false)
}
composeTestRule.onNode(isToggleable()).performClick()
composeTestRule.onNode(isToggleable()).assertIsOff()
}
@Test
fun clickable_canBeClick() {
var clicked = false
composeTestRule.setContent {
TestTwoTargetSwitchPreference(changeable = false) {
clicked = true
}
}
composeTestRule.onNodeWithText("TwoTargetSwitchPreference").performClick()
assertThat(clicked).isTrue()
}
}
@Composable
private fun TestTwoTargetSwitchPreference(
changeable: Boolean,
onClick: () -> Unit = {},
) {
var checked by rememberSaveable { mutableStateOf(false) }
TwoTargetSwitchPreference(
model = remember {
object : SwitchPreferenceModel {
override val title = "TwoTargetSwitchPreference"
override val checked = { checked }
override val changeable = { changeable }
override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
}
},
onClick = onClick,
)
}

View File

@@ -0,0 +1,455 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.scaffold
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.getUnclippedBoundsInRoot
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.height
import androidx.compose.ui.unit.width
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.rootWidth
import com.android.settingslib.spa.testutils.setContentForSizeAssertions
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@OptIn(ExperimentalMaterial3Api::class)
@RunWith(AndroidJUnit4::class)
class CustomizedAppBarTest {
@get:Rule
val rule = createComposeRule()
@Test
fun smallTopAppBar_expandsToScreen() {
rule
.setContentForSizeAssertions {
CustomizedTopAppBar(title = { Text("Title") })
}
.assertHeightIsEqualTo(ContainerHeight)
.assertWidthIsEqualTo(rule.rootWidth())
}
@Test
fun smallTopAppBar_withTitle() {
val title = "Title"
rule.setContent {
Box(Modifier.testTag(TopAppBarTestTag)) {
CustomizedTopAppBar(title = { Text(title) })
}
}
rule.onNodeWithText(title).assertIsDisplayed()
}
@Test
fun smallTopAppBar_default_positioning() {
rule.setContent {
Box(Modifier.testTag(TopAppBarTestTag)) {
CustomizedTopAppBar(
navigationIcon = {
FakeIcon(Modifier.testTag(NavigationIconTestTag))
},
title = {
Text("Title", Modifier.testTag(TitleTestTag))
},
actions = {
FakeIcon(Modifier.testTag(ActionsTestTag))
}
)
}
}
assertSmallDefaultPositioning()
}
@Test
fun smallTopAppBar_noNavigationIcon_positioning() {
rule.setContent {
Box(Modifier.testTag(TopAppBarTestTag)) {
CustomizedTopAppBar(
title = {
Text("Title", Modifier.testTag(TitleTestTag))
},
actions = {
FakeIcon(Modifier.testTag(ActionsTestTag))
}
)
}
}
assertSmallPositioningWithoutNavigation()
}
@Test
fun smallTopAppBar_titleDefaultStyle() {
var textStyle: TextStyle? = null
var expectedTextStyle: TextStyle? = null
rule.setContent {
CustomizedTopAppBar(
title = {
Text("Title")
textStyle = LocalTextStyle.current
expectedTextStyle = MaterialTheme.typography.titleMedium
},
)
}
assertThat(textStyle).isNotNull()
assertThat(textStyle).isEqualTo(expectedTextStyle)
}
@Test
fun smallTopAppBar_contentColor() {
var titleColor: Color = Color.Unspecified
var navigationIconColor: Color = Color.Unspecified
var actionsColor: Color = Color.Unspecified
var expectedTitleColor: Color = Color.Unspecified
var expectedNavigationIconColor: Color = Color.Unspecified
var expectedActionsColor: Color = Color.Unspecified
rule.setContent {
CustomizedTopAppBar(
navigationIcon = {
FakeIcon(Modifier.testTag(NavigationIconTestTag))
navigationIconColor = LocalContentColor.current
expectedNavigationIconColor =
TopAppBarDefaults.topAppBarColors().navigationIconContentColor
// fraction = 0f to indicate no scroll.
},
title = {
Text("Title", Modifier.testTag(TitleTestTag))
titleColor = LocalContentColor.current
expectedTitleColor = TopAppBarDefaults.topAppBarColors().titleContentColor
},
actions = {
FakeIcon(Modifier.testTag(ActionsTestTag))
actionsColor = LocalContentColor.current
expectedActionsColor =
TopAppBarDefaults.topAppBarColors().actionIconContentColor
}
)
}
assertThat(navigationIconColor).isNotNull()
assertThat(titleColor).isNotNull()
assertThat(actionsColor).isNotNull()
assertThat(navigationIconColor).isEqualTo(expectedNavigationIconColor)
assertThat(titleColor).isEqualTo(expectedTitleColor)
assertThat(actionsColor).isEqualTo(expectedActionsColor)
}
@Test
fun largeTopAppBar_scrolled_positioning() {
val content = @Composable { scrollBehavior: TopAppBarScrollBehavior? ->
Box(Modifier.testTag(TopAppBarTestTag)) {
CustomizedLargeTopAppBar(
navigationIcon = {
FakeIcon(Modifier.testTag(NavigationIconTestTag))
},
title = "Title",
actions = {
FakeIcon(Modifier.testTag(ActionsTestTag))
},
scrollBehavior = scrollBehavior,
)
}
}
assertLargeScrolledHeight(
MaxHeightWithoutTitle + DefaultTitleHeight,
MaxHeightWithoutTitle + DefaultTitleHeight,
content,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Test
fun topAppBar_enterAlways_allowHorizontalScroll() {
lateinit var state: LazyListState
rule.setContent {
state = rememberLazyListState()
MultiPageContent(TopAppBarDefaults.enterAlwaysScrollBehavior(), state)
}
rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
rule.runOnIdle {
assertThat(state.firstVisibleItemIndex).isEqualTo(1)
}
rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
rule.runOnIdle {
assertThat(state.firstVisibleItemIndex).isEqualTo(0)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Test
fun topAppBar_exitUntilCollapsed_allowHorizontalScroll() {
lateinit var state: LazyListState
rule.setContent {
state = rememberLazyListState()
MultiPageContent(TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), state)
}
rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
rule.runOnIdle {
assertThat(state.firstVisibleItemIndex).isEqualTo(1)
}
rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
rule.runOnIdle {
assertThat(state.firstVisibleItemIndex).isEqualTo(0)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Test
fun topAppBar_pinned_allowHorizontalScroll() {
lateinit var state: LazyListState
rule.setContent {
state = rememberLazyListState()
MultiPageContent(
TopAppBarDefaults.pinnedScrollBehavior(),
state
)
}
rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
rule.runOnIdle {
assertThat(state.firstVisibleItemIndex).isEqualTo(1)
}
rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
rule.runOnIdle {
assertThat(state.firstVisibleItemIndex).isEqualTo(0)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun MultiPageContent(scrollBehavior: TopAppBarScrollBehavior, state: LazyListState) {
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
CustomizedTopAppBar(
title = { Text(text = "Title") },
)
}
) { contentPadding ->
LazyRow(
Modifier
.fillMaxSize()
.testTag(LazyListTag), state
) {
items(2) { page ->
LazyColumn(
modifier = Modifier.fillParentMaxSize(),
contentPadding = contentPadding
) {
items(50) {
Text(
modifier = Modifier.fillParentMaxWidth(),
text = "Item #$page x $it"
)
}
}
}
}
}
}
/**
* Checks the app bar's components positioning when it's a [CustomizedTopAppBar]
* or a larger app bar that is scrolled up and collapsed into a small
* configuration and there is no navigation icon.
*/
private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) {
val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
val titleNode = rule.onNodeWithTag(TitleTestTag)
// Title should be vertically centered
titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
if (isCenteredTitle) {
// Title should be horizontally centered
titleNode.assertLeftPositionInRootIsEqualTo(
(appBarBounds.width - titleBounds.width) / 2
)
} else {
// Title should now be placed 16.dp from the start, as there is no navigation icon
// 4.dp padding for the whole app bar + 12.dp inset
titleNode.assertLeftPositionInRootIsEqualTo(4.dp + 12.dp)
}
rule.onNodeWithTag(ActionsTestTag)
// Action should still be placed at the end
.assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
}
/**
* Checks the app bar's components positioning when it's a [CustomizedTopAppBar].
*/
private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) {
val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
val appBarBottomEdgeY = appBarBounds.top + appBarBounds.height
rule.onNodeWithTag(NavigationIconTestTag)
// Navigation icon should be 4.dp from the start
.assertLeftPositionInRootIsEqualTo(AppBarStartAndEndPadding)
// Navigation icon should be centered within the height of the app bar.
.assertTopPositionInRootIsEqualTo(
appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
)
val titleNode = rule.onNodeWithTag(TitleTestTag)
// Title should be vertically centered
titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
if (isCenteredTitle) {
// Title should be horizontally centered
titleNode.assertLeftPositionInRootIsEqualTo(
(appBarBounds.width - titleBounds.width) / 2
)
} else {
// Title should be 56.dp from the start
// 4.dp padding for the whole app bar + 48.dp icon size + 4.dp title padding.
titleNode.assertLeftPositionInRootIsEqualTo(4.dp + FakeIconSize + 4.dp)
}
rule.onNodeWithTag(ActionsTestTag)
// Action should be placed at the end
.assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
// Action should be 8.dp from the top
.assertTopPositionInRootIsEqualTo(
appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
)
}
/**
* Checks that changing values at a [CustomizedLargeTopAppBar] scroll behavior
* affects the height of the app bar.
*
* This check partially and fully collapses the app bar to test its height.
*
* @param appBarMaxHeight the max height of the app bar [content]
* @param appBarMinHeight the min height of the app bar [content]
* @param content a Composable that adds a CustomizedLargeTopAppBar
*/
@OptIn(ExperimentalMaterial3Api::class)
private fun assertLargeScrolledHeight(
appBarMaxHeight: Dp,
appBarMinHeight: Dp,
content: @Composable (TopAppBarScrollBehavior?) -> Unit
) {
val fullyCollapsedOffsetDp = appBarMaxHeight - appBarMinHeight
val partiallyCollapsedOffsetDp = fullyCollapsedOffsetDp / 3
var partiallyCollapsedHeightOffsetPx = 0f
var fullyCollapsedHeightOffsetPx = 0f
lateinit var scrollBehavior: TopAppBarScrollBehavior
rule.setContent {
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
with(LocalDensity.current) {
partiallyCollapsedHeightOffsetPx = partiallyCollapsedOffsetDp.toPx()
fullyCollapsedHeightOffsetPx = fullyCollapsedOffsetDp.toPx()
}
content(scrollBehavior)
}
// Simulate a partially collapsed app bar.
rule.runOnIdle {
scrollBehavior.state.heightOffset = -partiallyCollapsedHeightOffsetPx
scrollBehavior.state.contentOffset = -partiallyCollapsedHeightOffsetPx
}
rule.waitForIdle()
rule.onNodeWithTag(TopAppBarTestTag)
.assertHeightIsEqualTo(
appBarMaxHeight - partiallyCollapsedOffsetDp
)
// Simulate a fully collapsed app bar.
rule.runOnIdle {
scrollBehavior.state.heightOffset = -fullyCollapsedHeightOffsetPx
// Simulate additional content scroll beyond the max offset scroll.
scrollBehavior.state.contentOffset =
-fullyCollapsedHeightOffsetPx - partiallyCollapsedHeightOffsetPx
}
rule.waitForIdle()
// Check that the app bar collapsed to its min height.
rule.onNodeWithTag(TopAppBarTestTag).assertHeightIsEqualTo(appBarMinHeight)
}
/**
* An [IconButton] with an [Icon] inside for testing positions.
*
* An [IconButton] is defaulted to be 48X48dp, while its child [Icon] is defaulted to 24x24dp.
*/
private val FakeIcon = @Composable { modifier: Modifier ->
IconButton(
onClick = { /* doSomething() */ },
modifier = modifier.semantics(mergeDescendants = true) {}
) {
Icon(ColorPainter(Color.Red), null)
}
}
private fun expectedActionPosition(appBarWidth: Dp): Dp =
appBarWidth - AppBarStartAndEndPadding - FakeIconSize
private val FakeIconSize = 48.dp
private val AppBarStartAndEndPadding = 4.dp
private val AppBarTopAndBottomPadding = (ContainerHeight - FakeIconSize) / 2
private val LazyListTag = "lazyList"
private val TopAppBarTestTag = "bar"
private val NavigationIconTestTag = "navigationIcon"
private val TitleTestTag = "title"
private val ActionsTestTag = "actions"
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.scaffold
import androidx.compose.material3.Text
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class HomeScaffoldTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun title_isDisplayed() {
composeTestRule.setContent {
HomeScaffold(title = TITLE) {
Text(text = "AAA")
Text(text = "BBB")
}
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun items_areDisplayed() {
composeTestRule.setContent {
HomeScaffold(title = TITLE) {
Text(text = "AAA")
Text(text = "BBB")
}
}
composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
}
private companion object {
const val TITLE = "title"
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.scaffold
import android.content.Context
import androidx.appcompat.R
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MoreOptionsTest {
@get:Rule
val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun moreOptionsAction_collapseAtBegin() {
composeTestRule.setContent {
MoreOptionsAction {
MenuItem(text = ITEM_TEXT) {}
}
}
composeTestRule.onNodeWithText(ITEM_TEXT).assertDoesNotExist()
}
@Test
fun moreOptionsAction_canExpand() {
composeTestRule.setContent {
MoreOptionsAction {
MenuItem(text = ITEM_TEXT) {}
}
}
composeTestRule.onNodeWithContentDescription(
context.getString(R.string.abc_action_menu_overflow_description)
).performClick()
composeTestRule.onNodeWithText(ITEM_TEXT).assertIsDisplayed()
}
@Test
fun moreOptionsAction_itemClicked() {
var menuItemClicked = false
composeTestRule.setContent {
MoreOptionsAction {
MenuItem(text = ITEM_TEXT) {
menuItemClicked = true
}
}
}
composeTestRule.onNodeWithContentDescription(
context.getString(R.string.abc_action_menu_overflow_description)
).performClick()
composeTestRule.onNodeWithText(ITEM_TEXT).performClick()
assertThat(menuItemClicked).isTrue()
}
private companion object {
const val ITEM_TEXT = "item text"
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.scaffold
import androidx.compose.material3.Text
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RegularScaffoldTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun regularScaffold_titleIsDisplayed() {
composeTestRule.setContent {
RegularScaffold(title = TITLE) {
Text(text = "AAA")
Text(text = "BBB")
}
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun regularScaffold_itemsAreDisplayed() {
composeTestRule.setContent {
RegularScaffold(title = TITLE) {
Text(text = "AAA")
Text(text = "BBB")
}
}
composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
}
private companion object {
const val TITLE = "title"
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.scaffold
import android.content.Context
import androidx.appcompat.R
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SearchScaffoldTest {
@get:Rule
val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun initialState_titleIsDisplayed() {
composeTestRule.setContent {
SearchScaffold(title = TITLE) { _, _ -> }
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun initialState_clearButtonNotExist() {
setContent()
onClearButton().assertDoesNotExist()
}
@Test
fun initialState_searchQueryIsEmpty() {
val searchQuery = setContent()
assertThat(searchQuery()).isEqualTo("")
}
@Test
fun canEnterSearchMode() {
val searchQuery = setContent()
clickSearchButton()
composeTestRule.onNodeWithText(TITLE).assertDoesNotExist()
onSearchHint().assertIsDisplayed()
onClearButton().assertDoesNotExist()
assertThat(searchQuery()).isEqualTo("")
}
@Test
fun canExitSearchMode() {
val searchQuery = setContent()
clickSearchButton()
composeTestRule.onNodeWithContentDescription(
context.getString(R.string.abc_toolbar_collapse_description)
).performClick()
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
onSearchHint().assertDoesNotExist()
onClearButton().assertDoesNotExist()
assertThat(searchQuery()).isEqualTo("")
}
@Test
fun canEnterSearchQuery() {
val searchQuery = setContent()
clickSearchButton()
onSearchHint().performTextInput(QUERY)
onClearButton().assertIsDisplayed()
assertThat(searchQuery()).isEqualTo(QUERY)
}
@Test
fun canClearSearchQuery() {
val searchQuery = setContent()
clickSearchButton()
onSearchHint().performTextInput(QUERY)
onClearButton().performClick()
onClearButton().assertDoesNotExist()
assertThat(searchQuery()).isEqualTo("")
}
private fun setContent(): () -> String {
lateinit var actualSearchQuery: () -> String
composeTestRule.setContent {
SearchScaffold(title = TITLE) { _, searchQuery ->
SideEffect {
actualSearchQuery = searchQuery
}
}
}
return actualSearchQuery
}
private fun clickSearchButton() {
composeTestRule.onNodeWithContentDescription(
context.getString(R.string.search_menu_title)
).performClick()
}
private fun onSearchHint() = composeTestRule.onNodeWithText(
context.getString(R.string.abc_search_hint)
)
private fun onClearButton() = composeTestRule.onNodeWithContentDescription(
context.getString(R.string.abc_searchview_description_clear)
)
private companion object {
const val TITLE = "title"
const val QUERY = "query"
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.scaffold
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.widget.ui.SettingsTitle
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsPagerTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun twoPage_initialState() {
setTwoPagesContent()
composeTestRule.onNodeWithText("Personal").assertIsSelected()
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
composeTestRule.onNodeWithText("Work").assertIsNotSelected()
composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
}
@Test
fun twoPage_afterSwitch() {
setTwoPagesContent()
composeTestRule.onNodeWithText("Work").performClick()
composeTestRule.onNodeWithText("Personal").assertIsNotSelected()
composeTestRule.onNodeWithText("Page 0").assertIsNotDisplayed()
composeTestRule.onNodeWithText("Work").assertIsSelected()
composeTestRule.onNodeWithText("Page 1").assertIsDisplayed()
}
@Test
fun onePage_initialState() {
composeTestRule.setContent {
SettingsPager(listOf("Personal")) {
SettingsTitle(title = "Page $it")
}
}
composeTestRule.onNodeWithText("Personal").assertDoesNotExist()
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
}
private fun setTwoPagesContent() {
composeTestRule.setContent {
SettingsPager(listOf("Personal", "Work")) {
SettingsTitle(title = "Page $it")
}
}
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.scaffold
import android.app.Activity
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.Text
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class SettingsScaffoldTest {
@get:Rule
val composeTestRule = createComposeRule()
@get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
private lateinit var activity: Activity
@Test
fun settingsScaffold_titleIsDisplayed() {
composeTestRule.setContent {
SettingsScaffold(title = TITLE) {
Text(text = "AAA")
Text(text = "BBB")
}
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun settingsScaffold_itemsAreDisplayed() {
composeTestRule.setContent {
SettingsScaffold(title = TITLE) {
Text(text = "AAA")
Text(text = "BBB")
}
}
composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
}
@Test
fun settingsScaffold_noHorizontalPadding() {
lateinit var actualPaddingValues: PaddingValues
composeTestRule.setContent {
SettingsScaffold(title = TITLE) { paddingValues ->
SideEffect {
actualPaddingValues = paddingValues
}
}
}
assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
}
@Test
fun activityTitle() {
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides activity) {
ActivityTitle(title = TITLE)
}
}
verify(activity).title = TITLE
}
private companion object {
const val TITLE = "Title"
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.scaffold
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.SignalCellularAlt
import androidx.compose.material3.Text
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SuwScaffoldTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun suwScaffold_titleIsDisplayed() {
composeTestRule.setContent {
SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) {
Text(text = "AAA")
Text(text = "BBB")
}
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun suwScaffold_itemsAreDisplayed() {
composeTestRule.setContent {
SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) {
Text(text = "AAA")
Text(text = "BBB")
}
}
composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
}
@Test
fun suwScaffold_actionButtonDisplayed() {
composeTestRule.setContent {
SuwScaffold(
imageVector = Icons.Outlined.SignalCellularAlt,
title = TITLE,
actionButton = BottomAppBarButton(TEXT) {},
) {}
}
composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
}
@Test
fun suwScaffold_dismissButtonDisplayed() {
composeTestRule.setContent {
SuwScaffold(
imageVector = Icons.Outlined.SignalCellularAlt,
title = TITLE,
dismissButton = BottomAppBarButton(TEXT) {},
) {}
}
composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
}
private companion object {
const val TITLE = "Title"
const val TEXT = "Text"
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.ui
import androidx.compose.runtime.remember
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CategoryTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun categoryTitle() {
composeTestRule.setContent {
CategoryTitle(title = "CategoryTitle")
}
composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed()
}
@Test
fun category_hasContent_titleDisplayed() {
composeTestRule.setContent {
Category(title = "CategoryTitle") {
Preference(remember {
object : PreferenceModel {
override val title = "Some Preference"
override val summary = { "Some summary" }
}
})
}
}
composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed()
}
@Test
fun category_noContent_titleNotDisplayed() {
composeTestRule.setContent {
Category(title = "CategoryTitle") {}
}
composeTestRule.onNodeWithText("CategoryTitle").assertDoesNotExist()
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.ui
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CopyableBodyTest {
@get:Rule
val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun text_isDisplayed() {
composeTestRule.setContent {
CopyableBody(TEXT)
}
composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
}
@Test
fun onLongPress_contextMenuDisplayed() {
composeTestRule.setContent {
CopyableBody(TEXT)
}
composeTestRule.onNodeWithText(TEXT).performTouchInput {
longClick()
}
composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).assertIsDisplayed()
}
@Test
fun onCopy_saveToClipboard() {
val clipboardManager = context.getSystemService(ClipboardManager::class.java)!!
clipboardManager.setPrimaryClip(ClipData.newPlainText("", ""))
composeTestRule.setContent {
CopyableBody(TEXT)
}
composeTestRule.onNodeWithText(TEXT).performTouchInput {
longClick()
}
composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).performClick()
assertThat(clipboardManager.primaryClip!!.getItemAt(0).text.toString()).isEqualTo(TEXT)
}
private companion object {
const val TEXT = "Text"
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.ui
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class FooterTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun footer_isEmpty() {
composeTestRule.setContent {
Footer(footerText = "")
}
composeTestRule.onRoot().assertIsNotDisplayed()
}
@Test
fun footer_notEmpty_displayed() {
composeTestRule.setContent {
Footer(footerText = FOOTER_TEXT)
}
composeTestRule.onNodeWithText(FOOTER_TEXT).assertIsDisplayed()
}
private companion object {
const val FOOTER_TEXT = "Footer text"
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.ui
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ProgressBarTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testProgressBar() {
composeTestRule.setContent {
LinearProgressBar(progress = .5f)
CircularProgressBar(progress = .2f)
}
fun progressEqualsTo(progress: Float): SemanticsMatcher =
SemanticsMatcher.expectValue(
SemanticsProperties.ProgressBarRangeInfo,
ProgressBarRangeInfo(progress, 0f..1f, 0)
)
composeTestRule.onNode(progressEqualsTo(0.5f)).assertIsDisplayed()
composeTestRule.onNode(progressEqualsTo(0.2f)).assertIsDisplayed()
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.ui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SpinnerTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun spinner_initialState() {
var selectedId by mutableStateOf(1)
composeTestRule.setContent {
Spinner(
options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
selectedId = selectedId,
setId = { selectedId = it },
)
}
composeTestRule.onNodeWithText("Option 1").assertIsDisplayed()
composeTestRule.onNodeWithText("Option 2").assertDoesNotExist()
assertThat(selectedId).isEqualTo(1)
}
@Test
fun spinner_canChangeState() {
var selectedId by mutableStateOf(1)
composeTestRule.setContent {
Spinner(
options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
selectedId = selectedId,
setId = { selectedId = it },
)
}
composeTestRule.onNodeWithText("Option 1").performClick()
composeTestRule.onNodeWithText("Option 2").performClick()
composeTestRule.onNodeWithText("Option 1").assertDoesNotExist()
composeTestRule.onNodeWithText("Option 2").assertIsDisplayed()
assertThat(selectedId).isEqualTo(2)
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.ui
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TextTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun settingsTitle() {
composeTestRule.setContent {
SettingsTitle(title = "myTitleValue")
}
composeTestRule.onNodeWithText("myTitleValue").assertIsDisplayed()
}
fun placeholderTitle() {
composeTestRule.setContent {
PlaceholderTitle(title = "myTitlePlaceholder")
}
composeTestRule.onNodeWithText("myTitlePlaceholder").assertIsDisplayed()
}
}