NaingAungLuu / form-conductor

A declarative form validation for Jetpack Compose
https://formconductor.naingaungluu.me
MIT License
59 stars 5 forks source link

Button not enabled during UI Test #48

Open myxzlpltk opened 1 year ago

myxzlpltk commented 1 year ago

I'm a beginner in android compose. I don't know what went wrong in my code. The button won't enabled during UI test but working during manual testing.

rule.onNode(signInButton).assertIsNotEnabled()

rule.onNode(emailField).assertIsNotFocused()
rule.onNode(emailField).performClick()
rule.onNode(emailField).performTextInput("test@gmail.com")
rule.onNode(emailField).performImeAction()

rule.onNode(signInButton).assertIsNotEnabled()

rule.onNode(passwordField).assertIsFocused()
rule.onNode(passwordField).performTextInput("password")
rule.onNode(passwordField).performImeAction()

rule.onNode(signInButton).assertIsEnabled() // Failed to assert because the button is disabled

Please advise. Thank you

Edit: I think i know the problem. Compose isn't recompose yet during check assertIsEnabled. But still didn't understand how to fix it

NaingAungLuu commented 1 year ago

@myxzlpltk Thanks for reaching out! Do you mind sharing your code for the UI (the part being tested by the test script) ?

myxzlpltk commented 1 year ago

Here is the UI

form(SignInForm::class) {
    field(SignInForm::email) {
        OutlinedTextField(
            modifier = Modifier
                .fillMaxWidth()
                .testTag("EmailField"),
            value = this.state.value?.value.orEmpty(),
            onValueChange = {
                Timber.d("Email changed: $it")
                this.setField(it)
            },
            isError = resultState.value is FieldResult.Error,
            supportingText = {
                if (resultState.value is FieldResult.Error) {
                    Text(text = stringResource(R.string.email_error))
                }
            },
            label = {
                Text(text = stringResource(R.string.email_address))
            },
            placeholder = {
                Text(text = stringResource(R.string.email_hint))
            },
            leadingIcon = {
                Icon(imageVector = Icons.Filled.Email, contentDescription = stringResource(R.string.email_address))
            },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Email,
                imeAction = ImeAction.Next,
            ),
            keyboardActions = KeyboardActions(
                onNext = { focusManager.moveFocus(FocusDirection.Down) }
            )
        )
    }

    field(SignInForm::password) {
        OutlinedTextField(
            modifier = Modifier
                .fillMaxWidth()
                .testTag("PasswordField"),
            value = this.state.value?.value.orEmpty(),
            onValueChange = this::setField,
            isError = resultState.value is FieldResult.Error,
            supportingText = {
                if (resultState.value is FieldResult.Error) {
                    Text(text = stringResource(R.string.password_error))
                }
            },
            label = {
                Text(text = stringResource(R.string.password))
            },
            placeholder = {
                Text(text = stringResource(R.string.password_hint))
            },
            leadingIcon = {
                Icon(imageVector = Icons.Filled.Lock, contentDescription = stringResource(R.string.password))
            },
            visualTransformation = if (state.isPasswordVisible) {
                VisualTransformation.None
            } else {
                PasswordVisualTransformation()
            },
            trailingIcon = {
                IconToggleButton(
                    checked = state.isPasswordVisible,
                    onCheckedChange = actions.setPasswordVisibility,
                ) {
                    Icon(
                        imageVector = if (state.isPasswordVisible) {
                            Icons.Filled.Visibility
                        } else {
                            Icons.Filled.VisibilityOff
                        },
                        contentDescription = stringResource(R.string.toggle_password_visibility),
                    )
                }
            },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Password,
                imeAction = ImeAction.Done,
            ),
            keyboardActions = KeyboardActions(
                onDone = { focusManager.clearFocus() }
            )
        )
    }
    Spacer(modifier = Modifier.height(8.dp))
    TextButton(
        onClick = actions.navigateToForgotPassword,
        modifier = Modifier.align(Alignment.End)
    ) {
        Text(text = stringResource(R.string.forgot_password_question))
    }
    Spacer(modifier = Modifier.height(8.dp))

    Button(
        onClick = actions.navigateToDashboard,
        modifier = Modifier
            .fillMaxWidth()
            .testTag("SignInButton"),
        enabled = this.formState.value is FormResult.Success
    ) {
        Text(text = stringResource(R.string.sign_in))
    }
}
myxzlpltk commented 1 year ago

Hi can you replicate my problem? It's like the library validate previous form state.

rule.onNode(signInButton).assertIsNotEnabled()

rule.onNode(emailField).assertIsNotFocused()
rule.onNode(emailField).performClick()
rule.onNode(emailField).performTextInput("test@gmail.com")
rule.onNode(emailField).performImeAction()

rule.onNode(signInButton).assertIsNotEnabled()

rule.onNode(passwordField).assertIsFocused()
rule.onNode(passwordField).performTextInput("password")
rule.onNode(passwordField).performTextReplacement("") // empty string
rule.onNode(passwordField).performImeAction()

rule.onNode(signInButton).assertIsEnabled() // Success

if i debug it the formState.value it will be FormResult.Success("test@gmail.com", "") while this should be illegal because the password shouldn't be empty

myxzlpltk commented 1 year ago

Please check this minimal, reproducible project with UI test (check androidTest folder) https://github.com/myxzlpltk/FormConductorUITest