From 299ec8c18ed1dfa806e1feb37b017e06fda0fbfd Mon Sep 17 00:00:00 2001 From: Sasikanth Miriyampalli Date: Fri, 6 Oct 2023 18:02:59 +0530 Subject: [PATCH] Add `InputAge` component --- .../dhis/common/screens/InputAgeScreen.kt | 86 ++++++- .../designsystem/component/AgeFieldHelper.kt | 14 +- .../ui/designsystem/component/InputAge.kt | 226 ++++++++++++++++++ .../ui/designsystem/component/InputField.kt | 18 +- .../component/internal/StringUtils.kt | 56 +++++ .../ui/designsystem/component/InputAgeTest.kt | 144 +++++++++++ .../internal/DateOfBirthTransformationTest.kt | 41 ++++ 7 files changed, 576 insertions(+), 9 deletions(-) create mode 100644 designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAge.kt create mode 100644 designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAgeTest.kt create mode 100644 designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateOfBirthTransformationTest.kt diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputAgeScreen.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputAgeScreen.kt index 0b9b6aa6d..f55210902 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputAgeScreen.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputAgeScreen.kt @@ -5,7 +5,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import org.hisp.dhis.mobile.ui.designsystem.component.AgeInputType import org.hisp.dhis.mobile.ui.designsystem.component.ColumnComponentContainer +import org.hisp.dhis.mobile.ui.designsystem.component.InputAge +import org.hisp.dhis.mobile.ui.designsystem.component.InputShellState import org.hisp.dhis.mobile.ui.designsystem.component.Orientation import org.hisp.dhis.mobile.ui.designsystem.component.RadioButtonData import org.hisp.dhis.mobile.ui.designsystem.component.SubTitle @@ -14,7 +17,7 @@ import org.hisp.dhis.mobile.ui.designsystem.component.TimeUnitValues @Composable fun InputAgeScreen() { - ColumnComponentContainer("Age Field components") { + ColumnComponentContainer { SubTitle("Horizontal Age Field Helper") var selectedFieldHorizontal by remember { mutableStateOf(RadioButtonData("0", selected = true, enabled = true, textInput = TimeUnitValues.YEARS.value)) @@ -22,5 +25,86 @@ fun InputAgeScreen() { TimeUnitSelector(Orientation.HORIZONTAL, TimeUnitValues.YEARS.value) { selectedFieldHorizontal = it } + + SubTitle("Input Age Component - Idle") + var inputType by remember { mutableStateOf(AgeInputType.None) } + + InputAge( + title = "Label", + inputType = inputType, + onCalendarActionClicked = { + // no-op + }, + onValueChanged = { newInputType -> + inputType = newInputType + }, + ) + + SubTitle("Input Age Component - Idle Disabled") + InputAge( + title = "Label", + inputType = AgeInputType.None, + state = InputShellState.DISABLED, + onCalendarActionClicked = { + // no-op + }, + onValueChanged = { newInputType -> + inputType = newInputType + }, + ) + + SubTitle("Input Age Component - Date Of Birth") + InputAge( + title = "Label", + inputType = AgeInputType.DateOfBirth("01011985"), + state = InputShellState.DISABLED, + onCalendarActionClicked = { + // no-op + }, + onValueChanged = { newInputType -> + inputType = newInputType + }, + ) + + SubTitle("Input Age Component - Date Of Birth Required Error") + InputAge( + title = "Label", + inputType = AgeInputType.DateOfBirth("010"), + state = InputShellState.ERROR, + isRequired = true, + onCalendarActionClicked = { + // no-op + }, + onValueChanged = { + // no-op + }, + ) + + SubTitle("Input Age Component - Age Disabled") + InputAge( + title = "Label", + inputType = AgeInputType.Age(value = "56", unit = TimeUnitValues.YEARS), + state = InputShellState.DISABLED, + onCalendarActionClicked = { + // no-op + }, + onValueChanged = { newInputType -> + inputType = newInputType + }, + ) + + SubTitle("Input Age Component - Age Required Error") + InputAge( + title = "Label", + inputType = AgeInputType.Age(value = "56", unit = TimeUnitValues.YEARS), + state = InputShellState.ERROR, + isRequired = true, + onCalendarActionClicked = { + // no-op + }, + onValueChanged = { + // no-op + }, + ) } } diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/AgeFieldHelper.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/AgeFieldHelper.kt index 5daf918a1..704c2ff37 100644 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/AgeFieldHelper.kt +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/AgeFieldHelper.kt @@ -25,18 +25,26 @@ import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor fun TimeUnitSelector( orientation: Orientation, optionSelected: String, + modifier: Modifier = Modifier, + enabled: Boolean = true, onClick: (RadioButtonData) -> Unit, ) { + val backgroundColor = if (enabled) { + SurfaceColor.Surface + } else { + SurfaceColor.DisabledSurface + } + RowComponentContainer( - modifier = Modifier - .background(color = SurfaceColor.Surface, Shape.SmallBottom) + modifier = modifier + .background(color = backgroundColor, Shape.SmallBottom) .padding( start = Spacing.Spacing8, end = Spacing.Spacing8, ), ) { val options = TimeUnitValues.values().map { - RadioButtonData(it.value, optionSelected == it.value, true, provideStringResource(it.value)) + RadioButtonData(it.value, optionSelected == it.value, enabled, provideStringResource(it.value)) } val selectedItem = options.find { it.selected diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAge.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAge.kt new file mode 100644 index 000000000..15dab7ab8 --- /dev/null +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAge.kt @@ -0,0 +1,226 @@ +package org.hisp.dhis.mobile.ui.designsystem.component + +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Event +import androidx.compose.material.icons.outlined.Cancel +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import org.hisp.dhis.mobile.ui.designsystem.component.AgeInputType.Age +import org.hisp.dhis.mobile.ui.designsystem.component.AgeInputType.DateOfBirth +import org.hisp.dhis.mobile.ui.designsystem.component.AgeInputType.None +import org.hisp.dhis.mobile.ui.designsystem.component.TimeUnitValues.YEARS +import org.hisp.dhis.mobile.ui.designsystem.component.internal.RegExValidations +import org.hisp.dhis.mobile.ui.designsystem.resource.provideStringResource +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing + +// Update [DateOfBirthTransformation] when updating the mask +// Check the usages before modifying +private const val DATE_OF_BIRTH_MASK = "DDMMYYYY" + +/** + * Input filed to enter date-of-birth or age + * + * @param title: Label of the component. + * @param inputType: The type of input : + * [None] : default, + * [DateOfBirth] : In ddmmyyyy format, + * [Age]: Age value with appropriate time unit + * @param onCalendarActionClicked: Callback to handle the action when the calendar icon is clicked. + * @param state: [InputShellState] + * @param supportingText: List of [SupportingTextData] that manages all the messages to be shown. + * @param isRequired: Mark this input as marked + * @param onValueChanged: Callback to receive changes in the input + */ +@Composable +fun InputAge( + title: String, + inputType: AgeInputType = None, + onCalendarActionClicked: () -> Unit, + modifier: Modifier = Modifier, + state: InputShellState = InputShellState.UNFOCUSED, + supportingText: List? = null, + isRequired: Boolean = false, + imeAction: ImeAction = ImeAction.Next, + dateOfBirthLabel: String = provideStringResource("date_birth"), + orLabel: String = provideStringResource("or"), + ageLabel: String = provideStringResource("age"), + onFocusChanged: ((Boolean) -> Unit) = {}, + onValueChanged: (AgeInputType) -> Unit, +) { + val maxAgeCharLimit = 3 + val allowedCharacters = RegExValidations.DATE_OF_BIRTH.regex + + val helperText = remember(inputType) { + when (inputType) { + None -> null + is DateOfBirth -> DATE_OF_BIRTH_MASK + is Age -> inputType.unit.value + } + } + val helperStyle = remember(inputType) { + when (inputType) { + None -> InputStyle.NONE + is DateOfBirth -> InputStyle.WITH_DATE_OF_BIRTH_HELPER + is Age -> InputStyle.WITH_HELPER_AFTER + } + } + + val calendarButton: (@Composable () -> Unit)? = if (inputType is DateOfBirth) { + @Composable { + SquareIconButton( + modifier = Modifier.testTag("INPUT_AGE_OPEN_CALENDAR_BUTTON"), + icon = { + Icon( + imageVector = Icons.Filled.Event, + contentDescription = null, + ) + }, + onClick = onCalendarActionClicked, + enabled = state != InputShellState.DISABLED, + ) + } + } else { + null + } + + InputShell( + modifier = modifier.testTag("INPUT_AGE"), + title = title, + state = state, + isRequiredField = isRequired, + onFocusChanged = onFocusChanged, + inputField = { + when (inputType) { + None -> { + TextButtonSelector( + modifier = Modifier.focusable(true) + .testTag("INPUT_AGE_MODE_SELECTOR"), + firstOptionText = dateOfBirthLabel, + onClickFirstOption = { + onValueChanged.invoke(DateOfBirth.EMPTY) + }, + middleText = orLabel, + secondOptionText = ageLabel, + onClickSecondOption = { + onValueChanged.invoke(Age.EMPTY) + }, + enabled = state != InputShellState.DISABLED, + ) + } + is DateOfBirth, is Age -> { + BasicTextField( + modifier = Modifier + .testTag("INPUT_AGE_TEXT_FIELD") + .fillMaxWidth(), + inputText = transformInputText(inputType), + helper = helperText, + isSingleLine = true, + helperStyle = helperStyle, + onInputChanged = { newText -> + if (newText.length > maxAgeCharLimit && inputType is Age) { + return@BasicTextField + } + + @Suppress("KotlinConstantConditions") + val newInputType: AgeInputType = when (inputType) { + is Age -> inputType.copy(value = newText) + is DateOfBirth -> updateDateOfBirth(inputType, newText) + None -> None + } + + if (allowedCharacters.containsMatchIn(newText) || newText.isBlank()) { + onValueChanged.invoke(newInputType) + } + }, + enabled = state != InputShellState.DISABLED, + state = state, + keyboardOptions = KeyboardOptions(imeAction = imeAction, keyboardType = KeyboardType.Number), + ) + } + } + }, + primaryButton = { + if (inputType != None && state != InputShellState.DISABLED) { + IconButton( + modifier = Modifier.testTag("INPUT_AGE_RESET_BUTTON").padding(Spacing.Spacing0), + icon = { + Icon( + imageVector = Icons.Outlined.Cancel, + contentDescription = "Icon Button", + ) + }, + onClick = { + onValueChanged.invoke(None) + }, + ) + } + }, + secondaryButton = calendarButton, + supportingText = { + supportingText?.forEach { label -> + SupportingText( + label.text, + label.state, + ) + } + }, + legend = { + if (inputType is Age) { + TimeUnitSelector( + modifier = Modifier.fillMaxWidth() + .testTag("INPUT_AGE_TIME_UNIT_SELECTOR"), + orientation = Orientation.HORIZONTAL, + optionSelected = YEARS.value, + enabled = state != InputShellState.DISABLED, + onClick = { itemData -> + val timeUnit = TimeUnitValues.entries + .first { it.value.contains(itemData.textInput!!, ignoreCase = true) } + + onValueChanged.invoke(inputType.copy(unit = timeUnit)) + }, + ) + } + }, + ) +} + +private fun transformInputText(inputType: AgeInputType): String { + return when (inputType) { + is Age -> inputType.value + is DateOfBirth -> inputType.value + None -> "" + } +} + +private fun updateDateOfBirth(inputType: DateOfBirth, newText: String): AgeInputType { + return if (newText.length <= DATE_OF_BIRTH_MASK.length) { + inputType.copy(value = newText) + } else { + inputType + } +} + +sealed interface AgeInputType { + data object None : AgeInputType + + data class DateOfBirth(val value: String) : AgeInputType { + companion object { + val EMPTY = DateOfBirth("") + } + } + + data class Age(val value: String, val unit: TimeUnitValues) : AgeInputType { + companion object { + val EMPTY = Age("", YEARS) + } + } +} diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputField.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputField.kt index 3449dad06..c575a1bd6 100644 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputField.kt +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputField.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.VisualTransformation +import org.hisp.dhis.mobile.ui.designsystem.component.internal.DateOfBirthTransformation import org.hisp.dhis.mobile.ui.designsystem.component.internal.PrefixTransformation import org.hisp.dhis.mobile.ui.designsystem.component.internal.SuffixTransformer import org.hisp.dhis.mobile.ui.designsystem.theme.Color.Blue300 @@ -91,11 +92,17 @@ fun BasicTextField( var visualTransformation = VisualTransformation.None if (helperStyle != InputStyle.NONE) { - if (helperStyle == InputStyle.WITH_HELPER_BEFORE) { - helper?.let { visualTransformation = PrefixTransformation(it, enabled) } - } else { - helper?.let { - visualTransformation = SuffixTransformer(it) + when (helperStyle) { + InputStyle.WITH_HELPER_BEFORE -> { + helper?.let { visualTransformation = PrefixTransformation(it, enabled) } + } + InputStyle.WITH_DATE_OF_BIRTH_HELPER -> { + helper?.let { visualTransformation = DateOfBirthTransformation(it) } + } + else -> { + helper?.let { + visualTransformation = SuffixTransformer(it) + } } } } @@ -153,5 +160,6 @@ fun BasicTextField( enum class InputStyle { WITH_HELPER_AFTER, WITH_HELPER_BEFORE, + WITH_DATE_OF_BIRTH_HELPER, NONE, } diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/StringUtils.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/StringUtils.kt index cdea45c03..8e803781d 100644 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/StringUtils.kt +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/StringUtils.kt @@ -1,6 +1,7 @@ package org.hisp.dhis.mobile.ui.designsystem.component.internal import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation @@ -62,6 +63,60 @@ internal class SuffixTransformer(val suffix: String) : VisualTransformation { } } +internal class DateOfBirthTransformation(private val mask: String) : VisualTransformation { + + companion object { + private const val SEPARATOR = "/" + } + + override fun filter(text: AnnotatedString): TransformedText { + return dateFilter(text) + } + + private fun dateFilter(text: AnnotatedString): TransformedText { + val trimmed = if (text.text.length > mask.length) text.text.substring(0..mask.length) else text.text + val output = buildAnnotatedString { + for (i in mask.indices) { + val dateChar = trimmed.getOrNull(i) + if (dateChar == null) { + append(AnnotatedString(mask[i].toString(), DHIS2SCustomTextStyles.inputFieldHelper)) + } else { + append(trimmed[i]) + } + + if (i % 2 == 1 && i < 4) { + val separator = if (dateChar != null) { + SEPARATOR + } else { + AnnotatedString(SEPARATOR, DHIS2SCustomTextStyles.inputFieldHelper) + } + append(separator) + } + } + } + + val offsetMapping = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + if (trimmed.lastIndex >= 0) { + if (offset <= 1) return offset + if (offset <= 3) return offset + 1 + if (offset <= 8) return offset + 2 + return 10 + } else { + return 0 + } + } + + override fun transformedToOriginal(offset: Int): Int { + if (offset > text.length) return text.length + return offset + } + } + + return TransformedText(output, offsetMapping) + } +} + enum class RegExValidations(val regex: Regex) { BRITISH_DECIMAL_NOTATION("""^(?!\.)(?!.*-[^0-9])(?!(?:[^.]*\.){3})[-0-9]*(?:\.[0-9]*)?$""".toRegex()), EUROPEAN_DECIMAL_NOTATION("""^(?!.*,.+,|.*-.*-)[0-9,-]*$""".toRegex()), @@ -74,4 +129,5 @@ enum class RegExValidations(val regex: Regex) { PHONE_NUMBER("^[+0-9-()]+$".toRegex()), LINK("((https?|ftp|smtp)://)?(www\\.)?[a-zA-Z0-9@:%._+~#=-]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_+.~#?&/=-]*)".toRegex()), EMAIL("^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}\$".toRegex()), + DATE_OF_BIRTH("^[0-9]+$".toRegex()), } diff --git a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAgeTest.kt b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAgeTest.kt new file mode 100644 index 000000000..c7ffc5ccf --- /dev/null +++ b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputAgeTest.kt @@ -0,0 +1,144 @@ +package org.hisp.dhis.mobile.ui.designsystem.component + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import org.junit.Rule +import org.junit.Test + +class InputAgeTest { + + @get:Rule + val rule = createComposeRule() + + @Test + fun modeSelectionShouldBeShownWhenComponentIsInitialised() { + rule.setContent { + InputAge( + title = "Label", + onCalendarActionClicked = { + // no-op + }, + ) { + // no-op + } + } + + rule.onNodeWithTag("INPUT_AGE_MODE_SELECTOR").assertExists() + rule.onNodeWithTag("INPUT_AGE_TEXT_FIELD").assertDoesNotExist() + rule.onNodeWithTag("INPUT_AGE_RESET_BUTTON").assertDoesNotExist() + rule.onNodeWithTag("INPUT_AGE_OPEN_CALENDAR_BUTTON").assertDoesNotExist() + rule.onNodeWithTag("INPUT_AGE_TIME_UNIT_SELECTOR").assertDoesNotExist() + } + + @Test + fun dateOfBirthFieldShouldBeShownCorrectly() { + rule.setContent { + InputAge( + title = "Label", + inputType = AgeInputType.DateOfBirth.EMPTY, + onCalendarActionClicked = { + // no-op + }, + ) { + // no-op + } + } + + rule.onNodeWithTag("INPUT_AGE_MODE_SELECTOR").assertDoesNotExist() + rule.onNodeWithTag("INPUT_AGE_TEXT_FIELD").assertExists() + rule.onNodeWithTag("INPUT_AGE_RESET_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_AGE_OPEN_CALENDAR_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_AGE_TIME_UNIT_SELECTOR").assertDoesNotExist() + } + + @Test + fun dateOfBirthFieldChangesShouldWorkCorrectly() { + var inputType by mutableStateOf(AgeInputType.None) + rule.setContent { + InputAge( + title = "Label", + inputType = AgeInputType.DateOfBirth.EMPTY, + onCalendarActionClicked = { + // no-op + }, + ) { + inputType = it + } + } + + rule.onNodeWithTag("INPUT_AGE_TEXT_FIELD").performTextInput("1002") + + assert(inputType == AgeInputType.DateOfBirth("1002")) + } + + @Test + fun ageFieldShouldBeShownCorrectly() { + rule.setContent { + InputAge( + title = "Label", + inputType = AgeInputType.Age.EMPTY, + onCalendarActionClicked = { + // no-op + }, + ) { + // no-op + } + } + + rule.onNodeWithTag("INPUT_AGE_MODE_SELECTOR").assertDoesNotExist() + rule.onNodeWithTag("INPUT_AGE_TEXT_FIELD").assertExists() + rule.onNodeWithTag("INPUT_AGE_RESET_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_AGE_OPEN_CALENDAR_BUTTON").assertDoesNotExist() + rule.onNodeWithTag("INPUT_AGE_TIME_UNIT_SELECTOR").assertExists() + } + + @Test + fun ageFieldChangesShouldWorkCorrectly() { + var inputType by mutableStateOf(AgeInputType.None) + rule.setContent { + InputAge( + title = "Label", + inputType = AgeInputType.Age.EMPTY, + onCalendarActionClicked = { + // no-op + }, + ) { + inputType = it + } + } + + rule.onNodeWithTag("INPUT_AGE_TEXT_FIELD").performTextInput("56") + + assert(inputType == AgeInputType.Age(value = "56", unit = TimeUnitValues.YEARS)) + } + + @Test + fun clickingOnRestButtonShouldResetMode() { + var inputType by mutableStateOf(AgeInputType.Age.EMPTY) + + rule.setContent { + InputAge( + title = "Label", + inputType = inputType, + onCalendarActionClicked = { + // no-op + }, + ) { + inputType = it + } + } + + rule.onNodeWithTag("INPUT_AGE_RESET_BUTTON").performClick() + + rule.onNodeWithTag("INPUT_AGE_MODE_SELECTOR").assertExists() + rule.onNodeWithTag("INPUT_AGE_TEXT_FIELD").assertDoesNotExist() + rule.onNodeWithTag("INPUT_AGE_RESET_BUTTON").assertDoesNotExist() + rule.onNodeWithTag("INPUT_AGE_OPEN_CALENDAR_BUTTON").assertDoesNotExist() + rule.onNodeWithTag("INPUT_AGE_TIME_UNIT_SELECTOR").assertDoesNotExist() + } +} diff --git a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateOfBirthTransformationTest.kt b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateOfBirthTransformationTest.kt new file mode 100644 index 000000000..eb0844d3d --- /dev/null +++ b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateOfBirthTransformationTest.kt @@ -0,0 +1,41 @@ +package org.hisp.dhis.mobile.ui.designsystem.component.internal + +import androidx.compose.ui.text.AnnotatedString +import org.junit.Test + +private const val DATE_OF_BIRTH_MASK = "DDMMYYYY" + +class DateOfBirthTransformationTest { + + private val transformation = DateOfBirthTransformation(DATE_OF_BIRTH_MASK) + + @Test + fun dateOfBirthTransformationShouldWorkCorrectly() { + val transformedText = transformation + .filter(AnnotatedString("10041985")) + .text + .toString() + + assert(transformedText == "10/04/1985") + } + + @Test + fun partialDateOfBirthTransformationShouldWorkCorrectly() { + val transformedText = transformation + .filter(AnnotatedString("100")) + .text + .toString() + + assert(transformedText == "10/0M/YYYY") + } + + @Test + fun emptyTextShouldDisplayDateOfBirthMask() { + val transformedText = transformation + .filter(AnnotatedString("")) + .text + .toString() + + assert(transformedText == "DD/MM/YYYY") + } +}