Skip to content

Commit

Permalink
Merge pull request #266 from orcaformastodon/add/navigation-bar-view
Browse files Browse the repository at this point in the history
Add `NavigationBarView` to `:platform:autos`
  • Loading branch information
jeanbarrossilva authored Mar 15, 2024
2 parents 4ae4d75 + 0482704 commit f3d30c2
Show file tree
Hide file tree
Showing 18 changed files with 1,117 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright © 2024 Orca
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If
* not, see https://www.gnu.org/licenses.
*/

package com.jeanbarrossilva.orca.platform.autos.test.theme

import androidx.compose.ui.test.junit4.createComposeRule
import com.jeanbarrossilva.orca.platform.autos.theme.AutosTheme
import org.junit.Rule
import org.junit.Test

internal class AutosThemeExtensionsTests {
@get:Rule val composeRule = createComposeRule()

@Test(expected = MissingThemingException::class)
fun throwsWhenContentIsRequiredToBeThemedButIsNot() {
composeRule.setContent { AutosTheme.require() }
}

@Test
fun doesNotThrowWhenContentIsRequiredToBeThemedAndIs() {
composeRule.setContent { AutosTheme { AutosTheme.require() } }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright © 2024 Orca
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If
* not, see https://www.gnu.org/licenses.
*/

package com.jeanbarrossilva.orca.platform.autos.test.theme

import androidx.compose.runtime.Composable
import com.jeanbarrossilva.orca.platform.autos.theme.AutosTheme

/**
* [IllegalStateException] thrown if some content is required to be themed with [AutosTheme] but
* isn't.
*
* @see AutosTheme.require
*/
class MissingThemingException internal constructor() :
IllegalStateException("AutosTheme was required but content wasn't themed.")

/**
* Requires the content to be themed.
*
* @throws MissingThemingException If [AutosTheme] isn't applied.
*/
@Composable
@Suppress("ComposableNaming")
@Throws(IllegalStateException::class)
fun AutosTheme.require() {
runCatching { spacings }
.onFailure {
if (it is IllegalStateException) {
throw MissingThemingException()
}
}
}
2 changes: 2 additions & 0 deletions platform/autos/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ android {

dependencies {
androidTestImplementation(project(":platform:autos-test"))
androidTestImplementation(project(":platform:testing"))
androidTestImplementation(libs.android.compose.ui.test.junit)
androidTestImplementation(libs.android.compose.ui.test.manifest)
androidTestImplementation(libs.android.test.core)
androidTestImplementation(libs.android.test.espresso.core)
androidTestImplementation(libs.android.test.runner)
androidTestImplementation(libs.assertk)
androidTestImplementation(libs.kotlin.test)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* Copyright © 2024 Orca
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If
* not, see https://www.gnu.org/licenses.
*/

package com.jeanbarrossilva.orca.platform.autos.kit.scaffold.bar.navigation.view

import android.content.res.ColorStateList
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.performClick
import androidx.compose.ui.viewinterop.AndroidView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import assertk.assertThat
import assertk.assertions.isTrue
import com.jeanbarrossilva.orca.platform.autos.R
import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.bar.navigation.NavigationBarDefaults
import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.bar.navigation.view.image.withDrawable
import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.bar.navigation.view.image.withImageTint
import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.bar.navigation.view.text.hasTextColors
import com.jeanbarrossilva.orca.platform.autos.test.kit.scaffold.bar.navigation.onTab
import com.jeanbarrossilva.orca.platform.autos.test.theme.require
import com.jeanbarrossilva.orca.platform.autos.theme.AutosTheme
import com.jeanbarrossilva.orca.platform.testing.context
import com.jeanbarrossilva.orca.platform.testing.emptyStringResourceID
import kotlin.test.Test
import org.junit.Rule

internal class NavigationBarViewTests {
@get:Rule val composeRule = createComposeRule()

@Test
fun composes() {
var hasComposed = false
composeRule.setContent {
CompositionLocalProvider(LocalInspectionMode provides true) {
AndroidView(::NavigationBarView) {
it.setOnCompositionListener {
hasComposed = true
it.setOnCompositionListener(null)
}
}
}
}
assertThat(hasComposed).isTrue()
}

@Test
fun isThemed() {
var hasBeenThemed = false
composeRule.setContent {
AndroidView(::NavigationBarView) {
it.setOnCompositionListener {
AutosTheme.require()
hasBeenThemed = true
it.setOnCompositionListener(null)
}
}
}
assertThat(hasBeenThemed).isTrue()
}

@Test
fun identifiesTitle() {
val view = NavigationBarView(context)
composeRule.setContent { AndroidView({ view }) { it.setTitle(":P") } }
onView(withId(view.titleViewID)).check(matches(isCompletelyDisplayed()))
}

@Test
fun titleIsColoredWithCurrentContentColor() {
val view = NavigationBarView(context)
composeRule.setContent { AndroidView({ view }) }
onView(withId(view.titleViewID))
.check(
matches(hasTextColors(ColorStateList.valueOf(NavigationBarDefaults.ContentColor.toArgb())))
)
}

@Test
fun setsTitle() {
val view = NavigationBarView(context)
composeRule.setContent { AndroidView({ view }) { it.setTitle(":)") } }
onView(withId(view.titleViewID)).check(matches(isDisplayed()))
}

@Test
fun addsTab() {
composeRule
.apply {
setContent {
AndroidView(::NavigationBarView) {
it.addTab(android.R.id.tabs, R.drawable.icon_home_outlined, emptyStringResourceID) {
false
}
}
}
}
.onTab()
.assertIsDisplayed()
}

@Test
fun selectsTab() {
var hasBeenSelected = false
composeRule
.apply {
setContent {
AndroidView(::NavigationBarView) {
it.addTab(android.R.id.tabs, R.drawable.icon_home_outlined, emptyStringResourceID) {
hasBeenSelected = true
true
}
}
}
}
.onTab()
.performClick()
assertThat(hasBeenSelected).isTrue()
}

@Test
fun setsCurrentTab() {
var hasBeenSet = false
composeRule.setContent {
AndroidView(::NavigationBarView) {
it.addTab(android.R.id.tabs, R.drawable.icon_home_outlined, emptyStringResourceID) {
hasBeenSet = true
true
}
it.setCurrentTab(android.R.id.tabs)
}
}
assertThat(hasBeenSet).isTrue()
}

@Test
fun identifiesActionButton() {
val view = NavigationBarView(context)
composeRule.setContent { AndroidView({ view }) }
onView(withId(view.actionButtonID)).check(matches(isCompletelyDisplayed()))
}

@Test
fun tintsActionIconWithCurrentContentColor() {
val view = NavigationBarView(context)
composeRule.setContent {
AndroidView({ view }) { it.setAction(R.drawable.icon_back, emptyStringResourceID) {} }
}
onView(withId(view.actionButtonID))
.check(
matches(withImageTint(ColorStateList.valueOf(NavigationBarDefaults.ContentColor.toArgb())))
)
}

@Test
fun setsActionIcon() {
val view = NavigationBarView(context)
composeRule.setContent {
AndroidView({ view }) { it.setAction(R.drawable.icon_back, emptyStringResourceID) {} }
}
onView(withId(view.actionButtonID))
.check(
matches(
withDrawable(R.drawable.icon_back) {
it.setTint(NavigationBarDefaults.ContentColor.toArgb())
}
)
)
}

@Test
fun describesAction() {
val view = NavigationBarView(context)
composeRule.setContent {
AndroidView({ view }) { it.setAction(R.drawable.icon_back, emptyStringResourceID) {} }
}
onView(withId(view.actionButtonID))
.check(matches(withContentDescription(emptyStringResourceID)))
}

@Test
fun clicksAction() {
val view = NavigationBarView(context)
var hasBeenClicked = false
composeRule.setContent {
AndroidView({ view }) {
it.setAction(R.drawable.icon_back, emptyStringResourceID) { hasBeenClicked = true }
}
}
onView(withId(view.actionButtonID)).perform(click())
assertThat(hasBeenClicked).isTrue()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright © 2024 Orca
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If
* not, see https://www.gnu.org/licenses.
*/

package com.jeanbarrossilva.orca.platform.autos.kit.scaffold.bar.navigation.view.image

import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import com.jeanbarrossilva.orca.platform.testing.context
import org.hamcrest.BaseMatcher
import org.hamcrest.Description
import org.hamcrest.Matcher

/**
* [Matcher] that matches an [ImageView] whose [Drawable] is the one to which the [resource] is a
* reference.
*
* @param resource Resource of the [Drawable] that the [ImageView] is expected to contain.
* @param transform Changes the [Drawable] to the state that it should be.
* @see ImageView.getDrawable
*/
private class WithDrawableMatcher(
@DrawableRes private val resource: Int,
private val transform: (Drawable) -> Unit
) : BaseMatcher<View>() {
/** [Drawable] with the [resource] that has been found. */
private var drawable: Drawable? = null

override fun describeTo(description: Description?) {
description
?.appendText("view.getDrawable() to match resource ID ")
?.appendValue(resource)
?.appendText("[")
?.appendValue(
runCatching { context.resources?.getResourceEntryName(resource) }
.onSuccess { drawable = ContextCompat.getDrawable(context, resource)?.apply(transform) }
.recover { if (it is Resources.NotFoundException) null else throw it }
.getOrThrow()
)
?.appendText("]")
}

override fun matches(item: Any?): Boolean {
return item is ImageView &&
(((resource == Resources.ID_NULL && item.drawable == null) ||
drawable?.constantState == item.drawable?.constantState ||
drawable?.toBitmap()?.sameAs(item.drawable?.toBitmap()) ?: false))
}
}

/**
* [Matcher] that matches an [ImageView] whose [Drawable] is the one to which the [resource] is a
* reference.
*
* @param resource Resource of the [Drawable] that the [ImageView] is expected to contain.
* @param transform Changes the [Drawable] to the state that it should be.
* @see ImageView.getDrawable
*/
internal fun withDrawable(
@DrawableRes resource: Int,
transform: (Drawable) -> Unit = {}
): Matcher<View> {
return WithDrawableMatcher(resource, transform)
}
Loading

0 comments on commit f3d30c2

Please sign in to comment.