Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SpeziViews #140

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package edu.stanford.bdh.engagehf.contact.data

import com.google.firebase.firestore.DocumentSnapshot
import edu.stanford.spezi.core.design.component.StringResource
import edu.stanford.spezi.core.design.views.personalInfo.PersonNameComponents
import edu.stanford.spezi.modules.contact.model.Contact
import edu.stanford.spezi.modules.contact.model.ContactOption
import edu.stanford.spezi.modules.contact.model.PersonNameComponents
import edu.stanford.spezi.modules.contact.model.call
import edu.stanford.spezi.modules.contact.model.email
import javax.inject.Inject
Expand All @@ -19,11 +19,12 @@ class ContactDocumentToContactMapper @Inject constructor() {
}
val components = contactName.split(", ")
val nameComponents = components.firstOrNull()?.split(" ")
val personNameComponents = PersonNameComponents(
givenName = nameComponents?.getOrNull(0),
familyName = nameComponents?.drop(1)
?.joinToString(" ") // assigning everything besides given name here
)
val personNameComponents =
PersonNameComponents(
givenName = nameComponents?.getOrNull(0),
familyName = nameComponents?.drop(1)
?.joinToString(" ") // assigning everything besides given name here
)
val title = components.lastOrNull()
val contactEmail = document.getString(CONTACT_EMAIL_FIELD)
val phone = document.getString(CONTACT_PHONE_FIELD)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
import edu.stanford.spezi.core.design.theme.SpeziTheme
import edu.stanford.spezi.core.design.theme.TextStyles
import edu.stanford.spezi.core.design.theme.ThemePreviews
import edu.stanford.spezi.core.design.views.personalInfo.PersonNameComponents
import edu.stanford.spezi.core.notification.R
import edu.stanford.spezi.modules.contact.ContactComposable
import edu.stanford.spezi.modules.contact.model.Contact
import edu.stanford.spezi.modules.contact.model.ContactOption
import edu.stanford.spezi.modules.contact.model.PersonNameComponents
import edu.stanford.spezi.modules.contact.model.call
import edu.stanford.spezi.modules.contact.model.email
import edu.stanford.spezi.modules.contact.model.website
Expand Down Expand Up @@ -109,7 +109,10 @@
ContactScreenViewModel.UiState.Error("An error occurred"),
ContactScreenViewModel.UiState.ContactLoaded(
contact = Contact(
name = PersonNameComponents(givenName = "Leland", familyName = "Stanford"),
name = PersonNameComponents(
givenName = "Leland",
familyName = "Stanford"

Check warning on line 114 in app/src/main/kotlin/edu/stanford/bdh/engagehf/contact/ui/ContactScreen.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/edu/stanford/bdh/engagehf/contact/ui/ContactScreen.kt#L112-L114

Added lines #L112 - L114 were not covered by tests
),
image = ImageResource.Vector(Icons.Default.AccountBox),
title = StringResource("University Founder"),
description = StringResource(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package edu.stanford.bdh.engagehf.contact.data
import com.google.common.truth.Truth.assertThat
import com.google.firebase.firestore.DocumentSnapshot
import edu.stanford.spezi.core.design.component.StringResource
import edu.stanford.spezi.modules.contact.model.PersonNameComponents
import edu.stanford.spezi.core.design.views.personalInfo.PersonNameComponents
import io.mockk.every
import io.mockk.mockk
import org.junit.Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package edu.stanford.spezi.core.design.personalInfo

import androidx.compose.ui.test.junit4.createComposeRule
import edu.stanford.spezi.core.design.personalInfo.composables.NameFieldsTestComposable
import edu.stanford.spezi.core.design.personalInfo.simulators.NameFieldsTestSimulator
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class NameFieldsTest {

@get:Rule
val composeTestRule = createComposeRule()

@Before
fun init() {
composeTestRule.setContent {
NameFieldsTestComposable()
}
}

@Test
fun testNameFields() {
nameFields {
assertTextExists("First Name")
assertTextExists("Last Name")

enterText("enter your first name", "Leland")
enterText("enter your last name", "Stanford")
}
}

private fun nameFields(block: NameFieldsTestSimulator.() -> Unit) {
NameFieldsTestSimulator(composeTestRule).apply(block)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package edu.stanford.spezi.core.design.personalInfo

import androidx.compose.ui.test.junit4.createComposeRule
import edu.stanford.spezi.core.design.personalInfo.composables.UserProfileTestComposable
import edu.stanford.spezi.core.design.personalInfo.simulators.UserProfileTestSimulator
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class UserProfileTest {

@get:Rule
val composeTestRule = createComposeRule()

@Before
fun init() {
composeTestRule.setContent {
UserProfileTestComposable()
}
}

@Test
fun testUserProfile() {
userProfile {
assertUserInitialsExists(true, "PS")
assertUserInitialsExists(true, "LS")
waitUntilUserInitialsDisappear("LS")
assertImageExists()
}
}

private fun userProfile(block: UserProfileTestSimulator.() -> Unit) {
UserProfileTestSimulator(composeTestRule).apply(block)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package edu.stanford.spezi.core.design.personalInfo.composables

import androidx.compose.foundation.layout.Column
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import edu.stanford.spezi.core.design.component.StringResource
import edu.stanford.spezi.core.design.views.personalInfo.PersonNameComponents
import edu.stanford.spezi.core.design.views.personalInfo.fields.NameFieldRow

@Composable
fun NameFieldsTestComposable() {
val name = remember { mutableStateOf(PersonNameComponents()) }

Column {
NameFieldRow(StringResource("First Name"), name, PersonNameComponents::givenName) {
Text("enter your first name")
}

HorizontalDivider()

NameFieldRow(StringResource("Last Name"), name, PersonNameComponents::familyName) {
Text("enter your last name")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package edu.stanford.spezi.core.design.personalInfo.composables

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import edu.stanford.spezi.core.design.component.ImageResource
import edu.stanford.spezi.core.design.views.personalInfo.PersonNameComponents
import edu.stanford.spezi.core.design.views.personalInfo.UserProfileComposable
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds

@Composable
fun UserProfileTestComposable() {
Column {
UserProfileComposable(
PersonNameComponents(
givenName = "Paul",
familyName = "Schmiedmayer"
),
Modifier.height(100.dp),
)
UserProfileComposable(
PersonNameComponents(
givenName = "Leland",
familyName = "Stanford"
),
Modifier.height(200.dp),
) {
delay(0.5.seconds)
ImageResource.Vector(Icons.Default.Person)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package edu.stanford.spezi.core.design.personalInfo.simulators

import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextInput

class NameFieldsTestSimulator(
private val composeTestRule: ComposeTestRule,
) {

fun assertTextExists(text: String) {
composeTestRule
.onNodeWithText(text)
.assertExists()
}

fun enterText(placeholder: String, text: String) {
composeTestRule
.onNodeWithText(placeholder)
.performTextInput(text)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package edu.stanford.spezi.core.design.personalInfo.simulators

import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText

class UserProfileTestSimulator(
private val composeTestRule: ComposeTestRule,
) {

fun assertUserInitialsExists(exists: Boolean, text: String) {
val node = composeTestRule
.onNodeWithText(text)
if (exists) {
node.assertExists()
} else {
node.assertDoesNotExist()
}
}

fun waitUntilUserInitialsDisappear(text: String, timeoutMillis: Long = 1_000) {
composeTestRule.waitUntil(timeoutMillis = timeoutMillis) {
composeTestRule.onAllNodesWithText(text)
.fetchSemanticsNodes().isEmpty()
}
}

fun assertImageExists() {
composeTestRule
.onAllNodesWithContentDescription("")
.assertCountEquals(1)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package edu.stanford.spezi.core.design.validation

import androidx.compose.ui.test.junit4.createComposeRule
import edu.stanford.spezi.core.design.validation.composables.FocusValidationRules
import edu.stanford.spezi.core.design.validation.simulators.FocusValidationRulesSimulator
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class FocusValidationRulesTest {

@get:Rule
val composeTestRule = createComposeRule()

@Before
fun init() {
composeTestRule.setContent {
FocusValidationRules()
}
}

@Test
fun testFocusValidationRules() {
focusValidationRules {
assertHasEngines(true)
assertInputValid(false)
assertPasswordMessageExists(false)
assertEmptyMessageExists(false)
clickValidateButton()
assertLastState(false)
assertPasswordMessageExists(true)
assertEmptyMessageExists(true)
}
}

private fun focusValidationRules(block: FocusValidationRulesSimulator.() -> Unit) {
FocusValidationRulesSimulator(composeTestRule).apply(block)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package edu.stanford.spezi.core.design.validation.composables

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Button
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import edu.stanford.spezi.core.design.component.StringResource
import edu.stanford.spezi.core.design.views.validation.Validate
import edu.stanford.spezi.core.design.views.validation.ValidationRule
import edu.stanford.spezi.core.design.views.validation.minimalPassword
import edu.stanford.spezi.core.design.views.validation.nonEmpty
import edu.stanford.spezi.core.design.views.validation.state.ReceiveValidation
import edu.stanford.spezi.core.design.views.validation.state.ValidationContext
import edu.stanford.spezi.core.design.views.validation.views.VerifiableTextField

enum class Field {
INPUT, NON_EMPTY_INPUT
}

@Composable
fun FocusValidationRules() {
val input = remember { mutableStateOf("") }
val nonEmptyInput = remember { mutableStateOf("") }
val validationContext = remember { mutableStateOf(ValidationContext()) }
val lastValid = remember { mutableStateOf<Boolean?>(null) }
val switchFocus = remember { mutableStateOf(false) }

ReceiveValidation(validationContext) {
Column {
Text("Has Engines: ${if (!validationContext.value.isEmpty) "Yes" else "No"}")
Text("Input Valid: ${if (validationContext.value.allInputValid) "Yes" else "No"}")
lastValid.value?.let { lastValid ->
Text("Last state: ${if (lastValid) "valid" else "invalid"}")
}
Button(
onClick = {
val newLastValid = validationContext.value
.validateHierarchy(switchFocus.value)
lastValid.value = newLastValid
}
) {
Text("Validate")
}
Row {
Text("Switch Focus")
Switch(switchFocus.value, onCheckedChange = { switchFocus.value = it })
}

Validate(input.value, rules = listOf(ValidationRule.minimalPassword)) {
VerifiableTextField(StringResource(Field.INPUT.name), input)
}

Validate(nonEmptyInput.value, rules = listOf(ValidationRule.nonEmpty)) {
VerifiableTextField(StringResource(Field.NON_EMPTY_INPUT.name), nonEmptyInput)
}
}
}
}
Loading
Loading