fix: 首次提交
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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/")
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user