From 671506d684e6a86e59c8998359e75f8aa9f4b358 Mon Sep 17 00:00:00 2001 From: Sasikanth Miriyampalli Date: Mon, 16 Oct 2023 17:20:50 +0530 Subject: [PATCH] ANDROAPP-5573-mobile-ui-Create-InputDate-component (#105) * Add support for passing visual transformation in `BasicTextField` * Rename `DateOfBirthTransformation` to `DateTransformation` * Move `DATE_MASK` inside `DateTransformation` * Rename `DATE_OF_BIRTH` regex to `DATE_TIME` fixup! a73dafd80fb5810cadea87a3893b652f4338165c * Add time transformation * Make date and time transformation classes public * Add a common interface for date time visual transformations * Add date time transformation * Add input date time component * Rename `ActionIconType` to `DateTimeActionIconType` * Update date time transformation mask * Run code format * Add legend data as param to `InputDateTime` * Display input reset button if the input value is not null or blank * Fix broken test * Run code formatting --- .../kotlin/org/hisp/dhis/common/App.kt | 2 + .../hisp/dhis/common/screens/Components.kt | 1 + .../common/screens/InputDateTimeScreen.kt | 81 +++++++++ .../ui/designsystem/component/InputAge.kt | 12 +- .../designsystem/component/InputDateTime.kt | 136 +++++++++++++++ .../ui/designsystem/component/InputField.kt | 18 +- .../component/internal/StringUtils.kt | 160 +++++++++++++++++- .../component/InputDateTimeTest.kt | 76 +++++++++ .../internal/DateTimeTransformationTest.kt | 39 +++++ ...ationTest.kt => DateTransformationTest.kt} | 12 +- .../internal/TimeTransformationTest.kt | 39 +++++ 11 files changed, 550 insertions(+), 26 deletions(-) create mode 100644 common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputDateTimeScreen.kt create mode 100644 designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTime.kt create mode 100644 designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTimeTest.kt create mode 100644 designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateTimeTransformationTest.kt rename designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/{DateOfBirthTransformationTest.kt => DateTransformationTest.kt} (68%) create mode 100644 designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/TimeTransformationTest.kt diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt index f33f62ce9..8b7beef5f 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt @@ -38,6 +38,7 @@ import org.hisp.dhis.common.screens.ImageBlockScreen import org.hisp.dhis.common.screens.InputAgeScreen import org.hisp.dhis.common.screens.InputBarCodeScreen import org.hisp.dhis.common.screens.InputCheckBoxScreen +import org.hisp.dhis.common.screens.InputDateTimeScreen import org.hisp.dhis.common.screens.InputDropDownScreen import org.hisp.dhis.common.screens.InputEmailScreen import org.hisp.dhis.common.screens.InputIntegerScreen @@ -179,6 +180,7 @@ fun Main() { Components.INPUT_ORG_UNIT -> InputOrgUnitScreen() Components.IMAGE_BLOCK -> ImageBlockScreen() Components.INPUT_DROPDOWN -> InputDropDownScreen() + Components.INPUT_DATE_TIME -> InputDateTimeScreen() } } } diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/Components.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/Components.kt index fbf5c135d..7b63e6516 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/Components.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/Components.kt @@ -50,4 +50,5 @@ enum class Components(val label: String) { INPUT_ORG_UNIT("Input Org. Unit"), IMAGE_BLOCK("Image Block"), INPUT_DROPDOWN("Input Dropdown"), + INPUT_DATE_TIME("Input Date Time"), } diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputDateTimeScreen.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputDateTimeScreen.kt new file mode 100644 index 000000000..1f212f412 --- /dev/null +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputDateTimeScreen.kt @@ -0,0 +1,81 @@ +package org.hisp.dhis.common.screens + +import androidx.compose.runtime.Composable +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.ColumnComponentContainer +import org.hisp.dhis.mobile.ui.designsystem.component.DateTimeActionIconType +import org.hisp.dhis.mobile.ui.designsystem.component.InputDateTime +import org.hisp.dhis.mobile.ui.designsystem.component.InputShellState +import org.hisp.dhis.mobile.ui.designsystem.component.internal.DateTimeTransformation +import org.hisp.dhis.mobile.ui.designsystem.component.internal.DateTransformation +import org.hisp.dhis.mobile.ui.designsystem.component.internal.TimeTransformation + +@Composable +fun InputDateTimeScreen() { + ColumnComponentContainer { + var date by remember { mutableStateOf("") } + var time by remember { mutableStateOf("") } + var dateTime by remember { mutableStateOf("") } + + InputDateTime( + title = "Label", + value = date, + visualTransformation = DateTransformation(), + actionIconType = DateTimeActionIconType.DATE, + onActionClicked = { + // no-op + }, + onValueChanged = { date = it }, + ) + + InputDateTime( + title = "Label", + value = time, + visualTransformation = TimeTransformation(), + actionIconType = DateTimeActionIconType.TIME, + onActionClicked = { + // no-op + }, + onValueChanged = { time = it }, + ) + + InputDateTime( + title = "Label", + value = dateTime, + visualTransformation = DateTimeTransformation(), + actionIconType = DateTimeActionIconType.DATE_TIME, + onActionClicked = { + // no-op + }, + onValueChanged = { dateTime = it }, + ) + + InputDateTime( + title = "Label", + value = "", + state = InputShellState.DISABLED, + onActionClicked = { + // no-op + }, + onValueChanged = { + // no-op + }, + ) + + InputDateTime( + title = "Label", + value = "", + isRequired = true, + state = InputShellState.ERROR, + onActionClicked = { + // no-op + }, + onValueChanged = { + // no-op + }, + ) + } +} 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 index 15dab7ab8..2bba32129 100644 --- 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 @@ -18,14 +18,11 @@ 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.DateTransformation.Companion.DATE_MASK 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 * @@ -57,12 +54,11 @@ fun InputAge( onValueChanged: (AgeInputType) -> Unit, ) { val maxAgeCharLimit = 3 - val allowedCharacters = RegExValidations.DATE_OF_BIRTH.regex + val allowedCharacters = RegExValidations.DATE_TIME.regex val helperText = remember(inputType) { when (inputType) { - None -> null - is DateOfBirth -> DATE_OF_BIRTH_MASK + None, is DateOfBirth -> null is Age -> inputType.unit.value } } @@ -202,7 +198,7 @@ private fun transformInputText(inputType: AgeInputType): String { } private fun updateDateOfBirth(inputType: DateOfBirth, newText: String): AgeInputType { - return if (newText.length <= DATE_OF_BIRTH_MASK.length) { + return if (newText.length <= DATE_MASK.length) { inputType.copy(value = newText) } else { inputType diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTime.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTime.kt new file mode 100644 index 000000000..c52553119 --- /dev/null +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTime.kt @@ -0,0 +1,136 @@ +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.filled.Schedule +import androidx.compose.material.icons.outlined.Cancel +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +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.internal.DateTimeVisualTransformation +import org.hisp.dhis.mobile.ui.designsystem.component.internal.DateTransformation +import org.hisp.dhis.mobile.ui.designsystem.component.internal.RegExValidations +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing + +/** + * Input field to enter date, time or date&time. It will format content based on given visual + * transformation + * + * @param title: Label of the component. + * @param value: Input of the component in the format of DDMMYYYY/HHMM/DDMMYYYYHHMM + * @param actionIconType: Type of action icon to display. [DateTimeActionIconType.DATE_TIME], [DateTimeActionIconType.DATE], [DateTimeActionIconType.TIME] + * @param onActionClicked: Callback to handle the action when the calendar icon is clicked. + * @param state: [InputShellState] + * @param legendData: [LegendData] + * @param supportingText: List of [SupportingTextData] that manages all the messages to be shown. + * @param isRequired: Mark this input as marked + * @param visualTransformation: Pass a visual transformation to format the date input visually. By default uses [DateTransformation] + * @param onValueChanged: Callback to receive changes in the input in the format of DDMMYYYY/HHMM/DDMMYYYYHHMM + */ +@Composable +fun InputDateTime( + title: String, + value: String?, + actionIconType: DateTimeActionIconType = DateTimeActionIconType.DATE_TIME, + onActionClicked: () -> Unit, + modifier: Modifier = Modifier, + state: InputShellState = InputShellState.UNFOCUSED, + legendData: LegendData? = null, + supportingText: List? = null, + isRequired: Boolean = false, + imeAction: ImeAction = ImeAction.Next, + visualTransformation: DateTimeVisualTransformation = DateTransformation(), + onFocusChanged: ((Boolean) -> Unit) = {}, + onValueChanged: (String) -> Unit, +) { + val allowedCharacters = RegExValidations.DATE_TIME.regex + + InputShell( + modifier = modifier.testTag("INPUT_DATE_TIME"), + title = title, + state = state, + isRequiredField = isRequired, + onFocusChanged = onFocusChanged, + inputField = { + BasicTextField( + modifier = Modifier + .testTag("INPUT_DATE_TIME_TEXT_FIELD") + .fillMaxWidth(), + inputText = value.orEmpty(), + isSingleLine = true, + onInputChanged = { newText -> + if (newText.length > visualTransformation.maskLength) { + return@BasicTextField + } + + if (allowedCharacters.containsMatchIn(newText) || newText.isBlank()) { + onValueChanged.invoke(newText) + } + }, + enabled = state != InputShellState.DISABLED, + state = state, + keyboardOptions = KeyboardOptions(imeAction = imeAction, keyboardType = KeyboardType.Number), + visualTransformation = visualTransformation, + ) + }, + primaryButton = { + if (!value.isNullOrBlank() && state != InputShellState.DISABLED) { + IconButton( + modifier = Modifier.testTag("INPUT_DATE_TIME_RESET_BUTTON").padding(Spacing.Spacing0), + icon = { + Icon( + imageVector = Icons.Outlined.Cancel, + contentDescription = "Icon Button", + ) + }, + onClick = { + onValueChanged.invoke("") + }, + ) + } + }, + secondaryButton = { + val icon = when (actionIconType) { + DateTimeActionIconType.DATE, DateTimeActionIconType.DATE_TIME -> Icons.Filled.Event + DateTimeActionIconType.TIME -> Icons.Filled.Schedule + } + + SquareIconButton( + modifier = Modifier.testTag("INPUT_DATE_TIME_ACTION_BUTTON") + .focusable(), + icon = { + Icon( + imageVector = icon, + contentDescription = null, + ) + }, + onClick = onActionClicked, + enabled = state != InputShellState.DISABLED, + ) + }, + supportingText = { + supportingText?.forEach { label -> + SupportingText( + label.text, + label.state, + ) + } + }, + legend = { + legendData?.let { + Legend(legendData, Modifier.testTag("INPUT_DATE_TIME_LEGEND")) + } + }, + ) +} + +enum class DateTimeActionIconType { + DATE, TIME, DATE_TIME +} 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 c575a1bd6..a133ea902 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,7 +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.DateTransformation 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 @@ -72,6 +72,9 @@ fun EmptyInput( * @param modifier to pass a modifier if necessary * @param state manages the color of cursor depending on the state of parent component * @param keyboardOptions manages the ImeAction to be shown on the keyboard + * @param visualTransformation manages custom visual transformation. When null is passed it + * will use the visual transformation created based on helper style, when a visual transformation + * is passed it will ignore the helper style. * @param onNextClicked gives access to the ImeAction event */ @OptIn(ExperimentalComposeUiApi::class) @@ -86,27 +89,30 @@ fun BasicTextField( modifier: Modifier = Modifier, state: InputShellState = InputShellState.FOCUSED, keyboardOptions: KeyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + visualTransformation: VisualTransformation? = null, onNextClicked: (() -> Unit)? = null, ) { val keyboardController = LocalSoftwareKeyboardController.current - var visualTransformation = VisualTransformation.None + var textFieldVisualTransformation = VisualTransformation.None if (helperStyle != InputStyle.NONE) { when (helperStyle) { InputStyle.WITH_HELPER_BEFORE -> { - helper?.let { visualTransformation = PrefixTransformation(it, enabled) } + helper?.let { textFieldVisualTransformation = PrefixTransformation(it, enabled) } } InputStyle.WITH_DATE_OF_BIRTH_HELPER -> { - helper?.let { visualTransformation = DateOfBirthTransformation(it) } + textFieldVisualTransformation = DateTransformation() } else -> { helper?.let { - visualTransformation = SuffixTransformer(it) + textFieldVisualTransformation = SuffixTransformer(it) } } } } + textFieldVisualTransformation = visualTransformation ?: textFieldVisualTransformation + val cursorColor by remember { if (state == InputShellState.UNFOCUSED || state == InputShellState.FOCUSED) { mutableStateOf(InputShellState.FOCUSED.color) @@ -151,7 +157,7 @@ fun BasicTextField( keyboardController?.hide() }, ), - visualTransformation = visualTransformation, + visualTransformation = textFieldVisualTransformation, cursorBrush = SolidColor(cursorColor), ) } 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 8e803781d..adee4f02b 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 @@ -63,23 +63,33 @@ internal class SuffixTransformer(val suffix: String) : VisualTransformation { } } -internal class DateOfBirthTransformation(private val mask: String) : VisualTransformation { +interface DateTimeVisualTransformation : VisualTransformation { + val maskLength: Int +} + +class DateTransformation : DateTimeVisualTransformation { companion object { private const val SEPARATOR = "/" + + // Check the usages before modifying + internal const val DATE_MASK = "DDMMYYYY" } + override val maskLength: Int + get() = DATE_MASK.length + 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 trimmed = if (text.text.length > DATE_MASK.length) text.text.substring(0..DATE_MASK.length) else text.text val output = buildAnnotatedString { - for (i in mask.indices) { + for (i in DATE_MASK.indices) { val dateChar = trimmed.getOrNull(i) if (dateChar == null) { - append(AnnotatedString(mask[i].toString(), DHIS2SCustomTextStyles.inputFieldHelper)) + append(AnnotatedString(DATE_MASK[i].toString(), DHIS2SCustomTextStyles.inputFieldHelper)) } else { append(trimmed[i]) } @@ -117,6 +127,146 @@ internal class DateOfBirthTransformation(private val mask: String) : VisualTrans } } +class TimeTransformation : DateTimeVisualTransformation { + + companion object { + private const val SEPARATOR = ":" + + // Check the usages before modifying + internal const val TIME_MASK = "HHMM" + } + + override val maskLength: Int + get() = TIME_MASK.length + + override fun filter(text: AnnotatedString): TransformedText { + return timeFilter(text) + } + + private fun timeFilter(text: AnnotatedString): TransformedText { + val trimmed = if (text.text.length > TIME_MASK.length) text.text.substring(0..TIME_MASK.length) else text.text + val output = buildAnnotatedString { + for (i in TIME_MASK.indices) { + val timeChar = trimmed.getOrNull(i) + if (timeChar == null) { + append(AnnotatedString(TIME_MASK[i].toString(), DHIS2SCustomTextStyles.inputFieldHelper)) + } else { + append(trimmed[i]) + } + + if (i == 1) { + val separator = if (timeChar != 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 + return offset + 1 + } else { + return 0 + } + } + + override fun transformedToOriginal(offset: Int): Int { + if (offset > text.length) return text.length + return offset + } + } + + return TransformedText(output, offsetMapping) + } +} + +class DateTimeTransformation : DateTimeVisualTransformation { + + companion object { + private const val SEPARATOR_DATE = "/" + private const val SEPARATOR_TIME = ":" + private const val SEPARATOR_DATE_TIME = " - " + + // Check the usages before modifying + internal const val DATETIME_MASK = "DDMMYYYYhhmm" + } + + override val maskLength: Int + get() = DATETIME_MASK.length + + override fun filter(text: AnnotatedString): TransformedText { + return dateTimeFilter(text) + } + + private fun dateTimeFilter(text: AnnotatedString): TransformedText { + val trimmed = if (text.text.length > DATETIME_MASK.length) text.text.substring(0..DATETIME_MASK.length) else text.text + val output = buildAnnotatedString { + for (i in DATETIME_MASK.indices) { + val char = trimmed.getOrNull(i) + if (char == null) { + append(AnnotatedString(DATETIME_MASK[i].toString(), DHIS2SCustomTextStyles.inputFieldHelper)) + } else { + append(trimmed[i]) + } + + when (i) { + 1, 3 -> { + val separator = if (char == null) { + AnnotatedString(SEPARATOR_DATE, DHIS2SCustomTextStyles.inputFieldHelper) + } else { + SEPARATOR_DATE + } + append(separator) + } + 7 -> { + val separator = if (char == null) { + AnnotatedString(SEPARATOR_DATE_TIME, DHIS2SCustomTextStyles.inputFieldHelper) + } else { + SEPARATOR_DATE_TIME + } + append(separator) + } + 9 -> { + val separator = if (char == null) { + AnnotatedString(SEPARATOR_TIME, DHIS2SCustomTextStyles.inputFieldHelper) + } else { + SEPARATOR_TIME + } + append(separator) + } + } + } + } + + val offsetMapping = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + return if (trimmed.lastIndex >= 0) { + if (offset <= 1) return offset + if (offset <= 3) return offset + 1 + if (offset < 8) return offset + 2 + if (offset == 8) return offset + 5 + if (offset <= 11) return offset + 6 + return 18 + } else { + 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()), @@ -129,5 +279,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()), + DATE_TIME("^[0-9]+$".toRegex()), } diff --git a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTimeTest.kt b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTimeTest.kt new file mode 100644 index 000000000..00e1fd20f --- /dev/null +++ b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputDateTimeTest.kt @@ -0,0 +1,76 @@ +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.hisp.dhis.mobile.ui.designsystem.component.InputDateTime +import org.junit.Rule +import org.junit.Test + +class InputDateTimeTest { + + @get:Rule + val rule = createComposeRule() + + @Test + fun dateTimeFieldChangesShouldWorkCorrectly() { + var input by mutableStateOf("") + rule.setContent { + InputDateTime( + title = "Label", + value = null, + onActionClicked = { + // no-op + }, + ) { + input = it + } + } + + rule.onNodeWithTag("INPUT_DATE_TIME_TEXT_FIELD").performTextInput("1002") + + assert(input == "1002") + } + + @Test + fun resetButtonShouldNotBeShownWhenTextIsEmpty() { + var input by mutableStateOf("") + + rule.setContent { + InputDateTime( + title = "Label", + value = null, + onActionClicked = { + // no-op + }, + ) { + input = it + } + } + + rule.onNodeWithTag("INPUT_DATE_TIME_RESET_BUTTON").assertDoesNotExist() + } + + @Test + fun clickingOnResetButtonShouldClearInput() { + var input by mutableStateOf("1002") + + rule.setContent { + InputDateTime( + title = "Label", + value = input, + onActionClicked = { + // no-op + }, + ) { + input = it + } + } + + rule.onNodeWithTag("INPUT_DATE_TIME_RESET_BUTTON").performClick() + + assert(input.isBlank()) + } +} diff --git a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateTimeTransformationTest.kt b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateTimeTransformationTest.kt new file mode 100644 index 000000000..9af3aad32 --- /dev/null +++ b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateTimeTransformationTest.kt @@ -0,0 +1,39 @@ +package org.hisp.dhis.mobile.ui.designsystem.component.internal + +import androidx.compose.ui.text.AnnotatedString +import org.junit.Test + +class DateTimeTransformationTest { + + private val transformation = DateTimeTransformation() + + @Test + fun timeTransformationShouldWorkCorrectly() { + val transformedText = transformation + .filter(AnnotatedString("100219941240")) + .text + .toString() + + assert(transformedText == "10/02/1994 - 12:40") + } + + @Test + fun partialTimeTransformationShouldWorkCorrectly() { + val transformedText = transformation + .filter(AnnotatedString("100219")) + .text + .toString() + + assert(transformedText == "10/02/19YY - hh:mm") + } + + @Test + fun emptyTextShouldDisplayDateMask() { + val transformedText = transformation + .filter(AnnotatedString("")) + .text + .toString() + + assert(transformedText == "DD/MM/YYYY - hh:mm") + } +} 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/DateTransformationTest.kt similarity index 68% rename from designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateOfBirthTransformationTest.kt rename to designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/DateTransformationTest.kt index eb0844d3d..4c606285f 100644 --- 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/DateTransformationTest.kt @@ -3,14 +3,12 @@ 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 DateTransformationTest { -class DateOfBirthTransformationTest { - - private val transformation = DateOfBirthTransformation(DATE_OF_BIRTH_MASK) + private val transformation = DateTransformation() @Test - fun dateOfBirthTransformationShouldWorkCorrectly() { + fun dateTransformationShouldWorkCorrectly() { val transformedText = transformation .filter(AnnotatedString("10041985")) .text @@ -20,7 +18,7 @@ class DateOfBirthTransformationTest { } @Test - fun partialDateOfBirthTransformationShouldWorkCorrectly() { + fun partialDateTransformationShouldWorkCorrectly() { val transformedText = transformation .filter(AnnotatedString("100")) .text @@ -30,7 +28,7 @@ class DateOfBirthTransformationTest { } @Test - fun emptyTextShouldDisplayDateOfBirthMask() { + fun emptyTextShouldDisplayDateMask() { val transformedText = transformation .filter(AnnotatedString("")) .text diff --git a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/TimeTransformationTest.kt b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/TimeTransformationTest.kt new file mode 100644 index 000000000..b968d9e0f --- /dev/null +++ b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/TimeTransformationTest.kt @@ -0,0 +1,39 @@ +package org.hisp.dhis.mobile.ui.designsystem.component.internal + +import androidx.compose.ui.text.AnnotatedString +import org.junit.Test + +class TimeTransformationTest { + + private val transformation = TimeTransformation() + + @Test + fun timeTransformationShouldWorkCorrectly() { + val transformedText = transformation + .filter(AnnotatedString("1045")) + .text + .toString() + + assert(transformedText == "10:45") + } + + @Test + fun partialTimeTransformationShouldWorkCorrectly() { + val transformedText = transformation + .filter(AnnotatedString("104")) + .text + .toString() + + assert(transformedText == "10:4M") + } + + @Test + fun emptyTextShouldDisplayDateMask() { + val transformedText = transformation + .filter(AnnotatedString("")) + .text + .toString() + + assert(transformedText == "HH:MM") + } +}