From 533fa4640e8a8e246b1ee488460061c71789a224 Mon Sep 17 00:00:00 2001 From: Siddharth Agarwal Date: Fri, 15 Sep 2023 14:54:54 +0530 Subject: [PATCH] create check box input component --- .../kotlin/org/hisp/dhis/common/App.kt | 2 + .../dhis/common/screens/CheckboxScreen.kt | 82 ++++-- .../hisp/dhis/common/screens/Components.kt | 1 + .../common/screens/InputCheckBoxScreen.kt | 104 +++++++ .../screens/previews/CheckboxPreview.kt | 15 +- .../ui/designsystem/component/CheckBox.kt | 18 +- .../designsystem/component/InputCheckBox.kt | 88 ++++++ .../component/InputCheckBoxTest.kt | 257 ++++++++++++++++++ 8 files changed, 526 insertions(+), 41 deletions(-) create mode 100644 common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputCheckBoxScreen.kt create mode 100644 designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputCheckBox.kt create mode 100644 designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputCheckBoxTest.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 da09ea305..4149b64b0 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/App.kt @@ -32,6 +32,7 @@ import org.hisp.dhis.common.screens.FormShellsScreen import org.hisp.dhis.common.screens.FormsComponentsScreen import org.hisp.dhis.common.screens.IconButtonScreen import org.hisp.dhis.common.screens.IconCardsScreen +import org.hisp.dhis.common.screens.InputCheckBoxScreen import org.hisp.dhis.common.screens.InputIntegerScreen import org.hisp.dhis.common.screens.InputLetterScreen import org.hisp.dhis.common.screens.InputLongTextScreen @@ -139,6 +140,7 @@ fun Main() { Components.BADGES -> BadgesScreen() Components.SWITCH -> SwitchScreen() Components.INPUT_RADIO_BUTTON -> InputRadioButtonScreen() + Components.INPUT_CHECK_BOX -> InputCheckBoxScreen() } } } diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/CheckboxScreen.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/CheckboxScreen.kt index 360be1b97..0883a18f4 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/CheckboxScreen.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/CheckboxScreen.kt @@ -4,6 +4,11 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import org.hisp.dhis.common.screens.previews.CheckboxPreview import org.hisp.dhis.common.screens.previews.TextCheckboxPreview @@ -21,24 +26,33 @@ fun CheckboxScreen() { val option3 = "Option 3" val option4 = "Option 4" - val state1 = true - val state2 = false - val state3 = true - val state4 = false + var state1 by remember { mutableStateOf(true) } + var state2 by remember { mutableStateOf(false) } + var state3 by remember { mutableStateOf(true) } + var state4 by remember { mutableStateOf(false) } - val checkBoxesStatesHorizontal = listOf( - CheckBoxData("4", checked = true, enabled = true, textInput = option1), - CheckBoxData("5", checked = false, enabled = true, textInput = option2), - CheckBoxData("6", checked = true, enabled = false, textInput = option3), - CheckBoxData("7", checked = false, enabled = false, textInput = option4), - ) + var state5 by remember { mutableStateOf(true) } + var state6 by remember { mutableStateOf(true) } + var state7 by remember { mutableStateOf(false) } + var state8 by remember { mutableStateOf(false) } - val checkBoxesStatesVertical = listOf( - CheckBoxData("0", checked = true, enabled = true, textInput = option1), - CheckBoxData("1", checked = false, enabled = true, textInput = option2), - CheckBoxData("2", checked = true, enabled = false, textInput = option3), - CheckBoxData("3", checked = false, enabled = false, textInput = option4), - ) + val checkBoxesStatesHorizontal = remember { + mutableStateListOf( + CheckBoxData("4", checked = true, enabled = true, textInput = option1), + CheckBoxData("5", checked = false, enabled = true, textInput = option2), + CheckBoxData("6", checked = true, enabled = false, textInput = option3), + CheckBoxData("7", checked = false, enabled = false, textInput = option4), + ) + } + + val checkBoxesStatesVertical = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = option1), + CheckBoxData("1", checked = false, enabled = true, textInput = option2), + CheckBoxData("2", checked = true, enabled = false, textInput = option3), + CheckBoxData("3", checked = false, enabled = false, textInput = option4), + ) + } ColumnComponentContainer( title = "CheckBox", @@ -46,35 +60,51 @@ fun CheckboxScreen() { SubTitle( text = "Text Check Box", ) - TextCheckboxPreview(state1, true, option1) - TextCheckboxPreview(state2, true, option2) - TextCheckboxPreview(state3, false, option3) - TextCheckboxPreview(state4, enabled = false, text = option4) + TextCheckboxPreview(state1, true, option1) { + state1 = it + } + TextCheckboxPreview(state2, true, option2) { state2 = it } + TextCheckboxPreview(state3, false, option3) { state3 = it } + TextCheckboxPreview(state4, enabled = false, text = option4) { state4 = it } Spacer(Modifier.size(Spacing.Spacing18)) SubTitle( text = "Simple Check Box", ) Row { - CheckboxPreview(true, enabled = true) - CheckboxPreview(true, enabled = false) + CheckboxPreview(state5, enabled = true) { state5 = it } + CheckboxPreview(state6, enabled = false) { state6 = it } } Row { - CheckboxPreview(false, enabled = true) - CheckboxPreview(false, enabled = false) + CheckboxPreview(state7, enabled = true) { state7 = it } + CheckboxPreview(state8, enabled = false) { state8 = it } } Spacer(Modifier.size(Spacing.Spacing18)) SubTitle( text = "Horizontal Check Box Block", ) - CheckBoxBlock(Orientation.HORIZONTAL, checkBoxesStatesHorizontal) {} + CheckBoxBlock( + Orientation.HORIZONTAL, + checkBoxesStatesHorizontal, + onItemChange = { checkBoxData -> + val index = checkBoxesStatesHorizontal.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxesStatesHorizontal[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + ) Spacer(Modifier.size(Spacing.Spacing18)) SubTitle( text = "Vertical Check Box Block", ) - CheckBoxBlock(Orientation.VERTICAL, checkBoxesStatesVertical) {} + CheckBoxBlock( + Orientation.VERTICAL, + checkBoxesStatesVertical, + onItemChange = { checkBoxData -> + val index = checkBoxesStatesVertical.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxesStatesVertical[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + ) }, ) } 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 bacc51319..b46a7ac1a 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 @@ -30,4 +30,5 @@ enum class Components(val label: String) { ICON_CARDS("Icon Cards"), INPUT_RADIO_BUTTON("Input Radio Button"), SWITCH("Switch"), + INPUT_CHECK_BOX("Input Check Box"), } diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputCheckBoxScreen.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputCheckBoxScreen.kt new file mode 100644 index 000000000..ec81816af --- /dev/null +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/InputCheckBoxScreen.kt @@ -0,0 +1,104 @@ +package org.hisp.dhis.common.screens + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import org.hisp.dhis.mobile.ui.designsystem.component.CheckBoxData +import org.hisp.dhis.mobile.ui.designsystem.component.ColumnComponentContainer +import org.hisp.dhis.mobile.ui.designsystem.component.InputCheckBox +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.SubTitle +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing + +@Composable +fun InputCheckBoxScreen() { + val option1 = "Option 1" + val option2 = "Option 2" + val option3 = "Option 3" + val option4 = "Option 4" + val option5 = "Option 5" + val option6 = "Option 6" + + val checkBoxDataItemsVertical = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = option1), + CheckBoxData("1", checked = false, enabled = true, textInput = option2), + CheckBoxData("2", checked = false, enabled = true, textInput = option3), + ) + } + + val checkBoxDataItemsError = remember { + mutableStateListOf( + CheckBoxData("3", checked = true, enabled = true, textInput = option1), + CheckBoxData("4", checked = false, enabled = true, textInput = option2), + CheckBoxData("5", checked = false, enabled = true, textInput = option3), + ) + } + + val checkBoxDataItemsDisabled = remember { + mutableStateListOf( + CheckBoxData("6", checked = true, enabled = true, textInput = option1), + CheckBoxData("7", checked = false, enabled = true, textInput = option2), + CheckBoxData("8", checked = false, enabled = true, textInput = option3), + ) + } + + val checkBoxDataItemsHorizontal = remember { + mutableStateListOf( + CheckBoxData("9", checked = true, enabled = true, textInput = option1), + CheckBoxData("10", checked = false, enabled = true, textInput = option2), + CheckBoxData("11", checked = false, enabled = true, textInput = option3), + CheckBoxData("12", checked = false, enabled = true, textInput = option4), + CheckBoxData("13", checked = false, enabled = true, textInput = option5), + CheckBoxData("14", checked = false, enabled = true, textInput = option6), + ) + } + + ColumnComponentContainer("Checkboxes") { + SubTitle("Vertical") + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataItemsVertical, + onItemChange = { checkBoxData -> + val index = checkBoxDataItemsVertical.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxDataItemsVertical[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + onClearSelection = { checkBoxDataItemsVertical.replaceAll { it.copy(checked = false) } }, + ) + Spacer(Modifier.size(Spacing.Spacing18)) + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataItemsError, + state = InputShellState.ERROR, + onItemChange = { checkBoxData -> + val index = checkBoxDataItemsError.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxDataItemsError[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + onClearSelection = { checkBoxDataItemsError.replaceAll { it.copy(checked = false) } }, + ) + Spacer(Modifier.size(Spacing.Spacing18)) + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataItemsDisabled, + state = InputShellState.DISABLED, + onItemChange = { }, + onClearSelection = { }, + ) + Spacer(Modifier.size(Spacing.Spacing18)) + SubTitle("Horizontal") + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataItemsHorizontal, + orientation = Orientation.HORIZONTAL, + onItemChange = { checkBoxData -> + val index = checkBoxDataItemsHorizontal.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxDataItemsHorizontal[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + onClearSelection = { checkBoxDataItemsHorizontal.replaceAll { it.copy(checked = false) } }, + ) + } +} diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/previews/CheckboxPreview.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/previews/CheckboxPreview.kt index 9244c17ff..81c613c24 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/previews/CheckboxPreview.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/previews/CheckboxPreview.kt @@ -5,10 +5,16 @@ import org.hisp.dhis.mobile.ui.designsystem.component.CheckBox import org.hisp.dhis.mobile.ui.designsystem.component.CheckBoxData @Composable -internal fun CheckboxPreview(checked: Boolean, enabled: Boolean = true) { +internal fun CheckboxPreview( + checked: Boolean, + enabled: Boolean = true, + changeOption: (Boolean) -> Unit, +) { CheckBox( CheckBoxData("", checked, enabled, null), - ) {} + ) { + changeOption(it) + } } @Composable @@ -16,8 +22,11 @@ internal fun TextCheckboxPreview( checked: Boolean, enabled: Boolean = true, text: String = "Option", + changeOption: (Boolean) -> Unit, ) { CheckBox( CheckBoxData("", checked, enabled, text), - ) {} + ) { + changeOption(it) + } } diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/CheckBox.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/CheckBox.kt index 41ee40b6a..5cd0a2d10 100644 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/CheckBox.kt +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/CheckBox.kt @@ -12,12 +12,10 @@ import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import org.hisp.dhis.mobile.ui.designsystem.theme.InternalSizeValues import org.hisp.dhis.mobile.ui.designsystem.theme.Outline import org.hisp.dhis.mobile.ui.designsystem.theme.Ripple @@ -41,9 +39,6 @@ fun CheckBox( modifier: Modifier = Modifier, onCheckedChange: ((Boolean) -> Unit), ) { - var isChecked by remember { - mutableStateOf(checkBoxData.checked) - } val interactionSource = if (checkBoxData.enabled) remember { MutableInteractionSource() } else MutableInteractionSource() val textColor = if (checkBoxData.enabled) { @@ -61,8 +56,7 @@ fun CheckBox( indication = null, onClick = { if (checkBoxData.enabled) { - isChecked = !isChecked - onCheckedChange.invoke(isChecked) + onCheckedChange.invoke(!checkBoxData.checked) } }, enabled = checkBoxData.enabled, @@ -71,17 +65,17 @@ fun CheckBox( ) { CompositionLocalProvider(LocalRippleTheme provides Ripple.CustomDHISRippleTheme) { Checkbox( - checked = isChecked, + checked = checkBoxData.checked, onCheckedChange = { if (checkBoxData.enabled) { - isChecked = it - onCheckedChange.invoke(isChecked) + onCheckedChange.invoke(!checkBoxData.checked) } }, interactionSource = interactionSource, enabled = checkBoxData.enabled, modifier = Modifier - .size(InternalSizeValues.Size40), + .size(InternalSizeValues.Size40) + .testTag("CHECK_BOX_${checkBoxData.uid}"), colors = CheckboxDefaults.colors( checkedColor = SurfaceColor.Primary, uncheckedColor = Outline.Dark, diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputCheckBox.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputCheckBox.kt new file mode 100644 index 000000000..900526119 --- /dev/null +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputCheckBox.kt @@ -0,0 +1,88 @@ +package org.hisp.dhis.mobile.ui.designsystem.component + +import androidx.compose.material.icons.Icons +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 + +/** + * DHIS2 Input Check Box. Wraps DHIS ยท [CheckBox]. + * @param title controls the text to be shown for the title + * @param checkBoxData Contains all the data that will be displayed, the list type is CheckBoxData, + * It's parameters are uid for identifying the component, checked for controlling which option is checked, + * enabled controls if the component is clickable and textInput displaying the option text. + * @param modifier allows a modifier to be passed externally + * @param orientation Controls how the check boxes will be displayed, HORIZONTAL for rows or + * VERTICAL for columns. + * @param state Manages the InputShell state + * @param supportingText is a list of SupportingTextData that + * manages all the messages to be shown + * @param legendData manages the legendComponent + * @param isRequired controls whether the field is mandatory or not + * @param onItemChange is a callback to notify which item has changed into the block. + * @param onClearSelection is a callback to notify all items has cleared into the block. + */ +@Composable +fun InputCheckBox( + title: String, + checkBoxData: List, + modifier: Modifier = Modifier, + orientation: Orientation = Orientation.VERTICAL, + state: InputShellState = InputShellState.UNFOCUSED, + supportingText: List? = null, + legendData: LegendData? = null, + isRequired: Boolean = false, + onItemChange: (CheckBoxData) -> Unit, + onClearSelection: () -> Unit, +) { + InputShell( + modifier = modifier.testTag("INPUT_CHECK_BOX"), + isRequiredField = isRequired, + title = title, + state = state, + legend = { + legendData?.let { + Legend(legendData, modifier.testTag("INPUT_CHECK_BOX_LEGEND")) + } + }, + supportingText = { + supportingText?.forEach { label -> + SupportingText( + label.text, + label.state, + modifier = modifier.testTag("INPUT_CHECK_BOX_SUPPORTING_TEXT"), + ) + } + }, + inputField = { + val updatedCheckBoxData = mutableListOf() + checkBoxData.forEach { + updatedCheckBoxData.add(it.copy(enabled = state != InputShellState.DISABLED && it.enabled)) + } + CheckBoxBlock( + orientation = orientation, + content = updatedCheckBoxData, + onItemChange = onItemChange, + ) + }, + primaryButton = { + val isClearButtonVisible = checkBoxData.firstOrNull { it.checked } != null && state != InputShellState.DISABLED + if (isClearButtonVisible) { + IconButton( + modifier = Modifier.testTag("INPUT_CHECK_BOX_CLEAR_BUTTON"), + icon = { + Icon( + imageVector = Icons.Outlined.Cancel, + contentDescription = "Icon Button", + ) + }, + onClick = { + onClearSelection.invoke() + }, + ) + } + }, + ) +} diff --git a/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputCheckBoxTest.kt b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputCheckBoxTest.kt new file mode 100644 index 000000000..e848bd24d --- /dev/null +++ b/designsystem/src/desktopTest/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/InputCheckBoxTest.kt @@ -0,0 +1,257 @@ +package org.hisp.dhis.mobile.ui.designsystem.component + +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertIsFocused +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.assertIsNotFocused +import androidx.compose.ui.test.isFocused +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor +import org.junit.Rule +import org.junit.Test + +class InputCheckBoxTest { + @get:Rule + val rule = createComposeRule() + + @Test + fun shouldDisplayInputCheckBoxCorrectly() { + rule.setContent { + val checkBoxDataList = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = "Option 1"), + CheckBoxData("1", checked = false, enabled = false, textInput = "Option 2"), + CheckBoxData("2", checked = false, enabled = true, textInput = "Option 3"), + ) + } + + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataList, + modifier = Modifier.testTag("INPUT_CHECK_BOX"), + onItemChange = { checkBoxData -> + val index = checkBoxDataList.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxDataList[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + onClearSelection = { checkBoxDataList.replaceAll { it.copy(checked = false) } }, + ) + } + rule.onNodeWithTag("INPUT_CHECK_BOX").assertExists() + rule.onNodeWithTag("INPUT_CHECK_BOX_LEGEND").assertDoesNotExist() + rule.onNodeWithTag("INPUT_CHECK_BOX_SUPPORTING_TEXT").assertDoesNotExist() + } + + @Test + fun shouldAllowUserSelectionWhenEnabled() { + rule.setContent { + val checkBoxDataList = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = "Option 1"), + CheckBoxData("1", checked = false, enabled = true, textInput = "Option 2"), + CheckBoxData("2", checked = false, enabled = true, textInput = "Option 3"), + ) + } + + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataList, + modifier = Modifier.testTag("INPUT_CHECK_BOX"), + onItemChange = { checkBoxData -> + val index = checkBoxDataList.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxDataList[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + onClearSelection = { checkBoxDataList.replaceAll { it.copy(checked = false) } }, + ) + } + rule.onNodeWithTag("INPUT_CHECK_BOX").assertExists() + rule.onNodeWithTag("CHECK_BOX_1").performClick() + rule.onNodeWithTag("CHECK_BOX_1").assertIsFocused() + } + + @Test + fun shouldNotAllowUserSelectionWhenDisabled() { + rule.setContent { + val checkBoxDataList = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = "Option 1"), + CheckBoxData("1", checked = false, enabled = false, textInput = "Option 2"), + CheckBoxData("2", checked = false, enabled = true, textInput = "Option 3"), + ) + } + + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataList, + modifier = Modifier.testTag("INPUT_CHECK_BOX"), + state = InputShellState.DISABLED, + onItemChange = { }, + onClearSelection = { }, + ) + } + rule.onNodeWithTag("INPUT_CHECK_BOX").assertExists() + rule.onNodeWithTag("CHECK_BOX_1").performClick() + rule.onNodeWithTag("CHECK_BOX_1").assertIsNotEnabled() + rule.onNodeWithTag("CHECK_BOX_1").assert(!isFocused()) + } + + @Test + fun shouldShowClearButtonWhenItemChecked() { + rule.setContent { + val checkBoxDataList = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = "Option 1"), + CheckBoxData("1", checked = false, enabled = false, textInput = "Option 2"), + CheckBoxData("2", checked = false, enabled = true, textInput = "Option 3"), + ) + } + + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataList, + modifier = Modifier.testTag("INPUT_CHECK_BOX"), + onItemChange = { checkBoxData -> + val index = checkBoxDataList.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxDataList[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + onClearSelection = { checkBoxDataList.replaceAll { it.copy(checked = false) } }, + ) + } + rule.onNodeWithTag("INPUT_CHECK_BOX").assertExists() + rule.onNodeWithTag("INPUT_CHECK_BOX_CLEAR_BUTTON").assertExists() + } + + @Test + fun shouldHideClearButtonWhenNoItemChecked() { + rule.setContent { + val checkBoxDataList = remember { + mutableStateListOf( + CheckBoxData("0", checked = false, enabled = true, textInput = "Option 1"), + CheckBoxData("1", checked = false, enabled = false, textInput = "Option 2"), + CheckBoxData("2", checked = false, enabled = true, textInput = "Option 3"), + ) + } + + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataList, + modifier = Modifier.testTag("INPUT_CHECK_BOX"), + onItemChange = { checkBoxData -> + val index = checkBoxDataList.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxDataList[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + onClearSelection = { checkBoxDataList.replaceAll { it.copy(checked = false) } }, + ) + } + rule.onNodeWithTag("INPUT_CHECK_BOX").assertExists() + rule.onNodeWithTag("INPUT_CHECK_BOX_CLEAR_BUTTON").assertDoesNotExist() + } + + @Test + fun shouldHideClearButtonWhenDisabled() { + rule.setContent { + val checkBoxDataList = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = "Option 1"), + CheckBoxData("1", checked = false, enabled = false, textInput = "Option 2"), + CheckBoxData("2", checked = false, enabled = true, textInput = "Option 3"), + ) + } + + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataList, + modifier = Modifier.testTag("INPUT_CHECK_BOX"), + state = InputShellState.DISABLED, + onItemChange = { }, + onClearSelection = { }, + ) + } + rule.onNodeWithTag("INPUT_CHECK_BOX").assertExists() + rule.onNodeWithTag("INPUT_CHECK_BOX_CLEAR_BUTTON").assertDoesNotExist() + } + + @Test + fun shouldClearSelectionWhenClearButtonIsClickedAndHideClearButton() { + rule.setContent { + val checkBoxDataList = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = "Option 1"), + CheckBoxData("1", checked = true, enabled = true, textInput = "Option 2"), + CheckBoxData("2", checked = false, enabled = true, textInput = "Option 3"), + ) + } + + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataList, + modifier = Modifier.testTag("INPUT_CHECK_BOX"), + onItemChange = { checkBoxData -> + val index = checkBoxDataList.withIndex().first { it.value.uid == checkBoxData.uid }.index + checkBoxDataList[index] = checkBoxData.copy(checked = !checkBoxData.checked) + }, + onClearSelection = { checkBoxDataList.replaceAll { it.copy(checked = false) } }, + ) + } + rule.onNodeWithTag("INPUT_CHECK_BOX").assertExists() + rule.onNodeWithTag("INPUT_CHECK_BOX_CLEAR_BUTTON").assertExists() + rule.onNodeWithTag("INPUT_CHECK_BOX_CLEAR_BUTTON").performClick() + rule.onNodeWithTag("CHECK_BOX_0").assertIsNotFocused() + rule.onNodeWithTag("CHECK_BOX_1").assertIsNotFocused() + rule.onNodeWithTag("INPUT_CHECK_BOX_CLEAR_BUTTON").assertDoesNotExist() + } + + @Test + fun shouldShowLegendCorrectly() { + rule.setContent { + val checkBoxDataList = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = "Option 1"), + CheckBoxData("1", checked = true, enabled = false, textInput = "Option 2"), + CheckBoxData("2", checked = false, enabled = true, textInput = "Option 3"), + ) + } + + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataList, + legendData = LegendData(SurfaceColor.CustomGreen, "Legend"), + onItemChange = { }, + onClearSelection = { }, + ) + } + + rule.onNodeWithTag("INPUT_CHECK_BOX").assertExists() + rule.onNodeWithTag("INPUT_CHECK_BOX_LEGEND").assertExists() + rule.onNodeWithTag("INPUT_CHECK_BOX_LEGEND").assertHasClickAction() + } + + @Test + fun shouldShowSupportingTextCorrectly() { + rule.setContent { + val checkBoxDataList = remember { + mutableStateListOf( + CheckBoxData("0", checked = true, enabled = true, textInput = "Option 1"), + CheckBoxData("1", checked = true, enabled = false, textInput = "Option 2"), + CheckBoxData("2", checked = false, enabled = true, textInput = "Option 3"), + ) + } + + InputCheckBox( + title = "Label", + checkBoxData = checkBoxDataList, + supportingText = listOf(SupportingTextData("Supporting text", SupportingTextState.DEFAULT)), + onItemChange = { }, + onClearSelection = { }, + ) + } + rule.onNodeWithTag("INPUT_CHECK_BOX").assertExists() + rule.onNodeWithTag("INPUT_CHECK_BOX_SUPPORTING_TEXT").assertExists() + } +}