diff --git a/authenticator-screenshots/.gitignore b/authenticator-screenshots/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/authenticator-screenshots/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/authenticator-screenshots/README.md b/authenticator-screenshots/README.md deleted file mode 100644 index cd657c05..00000000 --- a/authenticator-screenshots/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Authenticator Screenshots - -Generates screenshots for the [Authenticator](../authenticator) project. - -```shell -# Record screenshots -./gradlew authenticator-screenshots:recordPaparazziDebug - -# Verify screenshots -./gradlew authenticator-screenshots:verifyPaparazziDebug -``` - -## Why a separate module? - -Paparazzi currently [has an issue](https://github.com/cashapp/paparazzi/issues/622) that prevents it from co-habitating with Robolectric tests. diff --git a/authenticator-screenshots/build.gradle.kts b/authenticator-screenshots/build.gradle.kts deleted file mode 100644 index a0373ea9..00000000 --- a/authenticator-screenshots/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -plugins { - id("amplify.android.library") - alias(libs.plugins.paparazzi) -} - -android { - namespace = "com.amplifyframework.ui.authenticator.screenshots" - - compileOptions { - isCoreLibraryDesugaringEnabled = true - } -} - -dependencies { - implementation(platform(libs.androidx.compose.bom)) - - implementation(libs.bundles.compose) - implementation(libs.test.mockk) - implementation(projects.authenticator) - - coreLibraryDesugaring(libs.android.desugar) -} - -// Verify screenshots when running the check task -tasks.named("check").configure { - dependsOn(tasks.first { it.name == "verifyPaparazzi" }) -} diff --git a/authenticator-screenshots/src/main/AndroidManifest.xml b/authenticator-screenshots/src/main/AndroidManifest.xml deleted file mode 100644 index dbd6e50d..00000000 --- a/authenticator-screenshots/src/main/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt deleted file mode 100644 index ce00f61e..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.ui.authenticator - -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldError -import com.amplifyframework.ui.authenticator.forms.FieldValidator -import com.amplifyframework.ui.authenticator.forms.MutableFieldData -import com.amplifyframework.ui.authenticator.forms.MutableFieldState -import com.amplifyframework.ui.authenticator.forms.MutableFormState -import com.amplifyframework.ui.authenticator.forms.MutablePasswordFieldState - -fun mockForm( - vararg fields: MutableFieldData, - enabled: Boolean = true -) = object : MutableFormState { - override val fields = fields.associateBy { it.config.key } - override var enabled = enabled -} - -fun mockFieldData( - config: FieldConfig, - state: MutableFieldState = mockFieldState(), - validator: FieldValidator = { null } -) = object : MutableFieldData { - override val state = state - override val config = config - override val validator = validator -} - -fun mockFieldState( - content: String = "", - error: FieldError? = null -) = object : MutableFieldState { - override var content = content - override var error = error -} - -fun mockPasswordFieldState( - content: String = "", - error: FieldError? = null, - visible: Boolean = false -) = object : MutablePasswordFieldState { - override var fieldContentVisible = visible - override var content = content - override var error: FieldError? = error -} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetScreenshots.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetScreenshots.kt deleted file mode 100644 index 2bcfba58..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetScreenshots.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.ui.authenticator.ui - -import com.amplifyframework.ui.authenticator.PasswordResetState -import com.amplifyframework.ui.authenticator.ScreenshotTestBase -import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep -import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldError -import com.amplifyframework.ui.authenticator.forms.FieldError.NotFound -import com.amplifyframework.ui.authenticator.forms.FieldKey -import com.amplifyframework.ui.authenticator.forms.MutableFormState -import com.amplifyframework.ui.authenticator.mockFieldData -import com.amplifyframework.ui.authenticator.mockFieldState -import com.amplifyframework.ui.authenticator.mockForm -import org.junit.Test - -class PasswordResetScreenshots : ScreenshotTestBase() { - - @Test - fun default_state() { - screenshot { - PasswordReset(state = mockPasswordResetState()) - } - } - - @Test - fun username_not_found() { - screenshot { - PasswordReset(state = mockPasswordResetState(error = NotFound)) - } - } - - private fun mockPasswordResetState( - error: FieldError? = null - ) = object : PasswordResetState { - override fun moveTo(step: AuthenticatorInitialStep) {} - override suspend fun submitPasswordReset() {} - override val form: MutableFormState = mockForm( - mockFieldData(FieldConfig.Text(FieldKey.Username), state = mockFieldState(error = error)) - ) - override val step = AuthenticatorStep.PasswordReset - } -} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmMfa.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmMfa.kt deleted file mode 100644 index 6f2b2957..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmMfa.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.ui.authenticator.ui - -import com.amplifyframework.auth.AuthCodeDeliveryDetails -import com.amplifyframework.ui.authenticator.ScreenshotTestBase -import com.amplifyframework.ui.authenticator.SignInConfirmMfaState -import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep -import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldError -import com.amplifyframework.ui.authenticator.forms.FieldKey -import com.amplifyframework.ui.authenticator.mockFieldData -import com.amplifyframework.ui.authenticator.mockFieldState -import com.amplifyframework.ui.authenticator.mockForm -import org.junit.Test - -class SignInConfirmMfa : ScreenshotTestBase() { - - @Test - fun sign_in_confirm_email_mfa_default() { - screenshot { - SignInConfirmMfa( - mockSignInConfirmMfaState( - AuthCodeDeliveryDetails( - "email@email.com", - AuthCodeDeliveryDetails.DeliveryMedium.EMAIL - ) - ) - ) - } - } - - @Test - fun sign_in_confirm_email_mfa_incorrect_code() { - screenshot { - SignInConfirmMfa( - mockSignInConfirmMfaState( - AuthCodeDeliveryDetails( - "email@email.com", - AuthCodeDeliveryDetails.DeliveryMedium.EMAIL - ), - "123456", - FieldError.ConfirmationCodeIncorrect - ) - ) - } - } - - @Test - fun sign_in_confirm_sms_mfa_default() { - screenshot { - SignInConfirmMfa( - mockSignInConfirmMfaState( - AuthCodeDeliveryDetails( - "123-123-1234", - AuthCodeDeliveryDetails.DeliveryMedium.SMS - ) - ) - ) - } - } - - @Test - fun sign_in_confirm_sms_mfa_incorrect_code() { - screenshot { - SignInConfirmMfa( - mockSignInConfirmMfaState( - AuthCodeDeliveryDetails( - "123-123-1234", - AuthCodeDeliveryDetails.DeliveryMedium.SMS - ), - "123456", - FieldError.ConfirmationCodeIncorrect - ) - ) - } - } - - private fun mockSignInConfirmMfaState( - deliveryDetails: AuthCodeDeliveryDetails, - confirmationCode: String = "", - fieldError: FieldError? = null - ) = object : SignInConfirmMfaState { - override val form = mockForm( - mockFieldData( - config = FieldConfig.Text(FieldKey.ConfirmationCode), - state = mockFieldState(content = confirmationCode, error = fieldError) - ) - ) - override val deliveryDetails: AuthCodeDeliveryDetails - get() = deliveryDetails - - override fun moveTo(step: AuthenticatorInitialStep) {} - override suspend fun confirmSignIn() { - TODO("Not yet implemented") - } - - override val step = AuthenticatorStep.SignInContinueWithEmailSetup - } -} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmTotpCodeScreenshots.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmTotpCodeScreenshots.kt deleted file mode 100644 index 61012e3a..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmTotpCodeScreenshots.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.ui.authenticator.ui - -import com.amplifyframework.ui.authenticator.ScreenshotTestBase -import com.amplifyframework.ui.authenticator.SignInConfirmTotpCodeState -import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep -import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldError -import com.amplifyframework.ui.authenticator.forms.FieldKey -import com.amplifyframework.ui.authenticator.mockFieldData -import com.amplifyframework.ui.authenticator.mockFieldState -import com.amplifyframework.ui.authenticator.mockForm -import org.junit.Test - -class SignInConfirmTotpCodeScreenshots : ScreenshotTestBase() { - - @Test - fun default_state() { - screenshot { - SignInConfirmTotpCode(state = mockSignInConfirmTotpCodeState()) - } - } - - @Test - fun invalid_code() { - screenshot { - SignInConfirmTotpCode( - state = mockSignInConfirmTotpCodeState(fieldError = FieldError.ConfirmationCodeIncorrect) - ) - } - } - - private fun mockSignInConfirmTotpCodeState( - confirmationCode: String = "", - fieldError: FieldError? = null - ) = object : SignInConfirmTotpCodeState { - override val form = mockForm( - mockFieldData( - config = FieldConfig.Text(FieldKey.ConfirmationCode), - state = mockFieldState(content = confirmationCode, error = fieldError) - ) - ) - - override fun moveTo(step: AuthenticatorInitialStep) {} - override suspend fun confirmSignIn() {} - override val step = AuthenticatorStep.SignInConfirmTotpCode - } -} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupScreenshots.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupScreenshots.kt deleted file mode 100644 index bceb39af..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupScreenshots.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.ui.authenticator.ui - -import com.amplifyframework.ui.authenticator.ScreenshotTestBase -import com.amplifyframework.ui.authenticator.SignInContinueWithEmailSetupState -import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep -import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldKey -import com.amplifyframework.ui.authenticator.mockFieldData -import com.amplifyframework.ui.authenticator.mockFieldState -import com.amplifyframework.ui.authenticator.mockForm -import org.junit.Test - -class SignInContinueWithEmailSetupScreenshots : ScreenshotTestBase() { - - @Test - fun default_state() { - screenshot { - SignInContinueWithEmailSetup(mockSignInContinueWithEmailSetupState()) - } - } - - private fun mockSignInContinueWithEmailSetupState() = object : SignInContinueWithEmailSetupState { - override val form = mockForm( - mockFieldData( - config = FieldConfig.Text(FieldKey.Email), - state = mockFieldState(content = "email@email.com") - ) - ) - override fun moveTo(step: AuthenticatorInitialStep) {} - override suspend fun continueSignIn() {} - override val step = AuthenticatorStep.SignInContinueWithEmailSetup - } -} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionScreenshots.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionScreenshots.kt deleted file mode 100644 index f6ea55ee..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionScreenshots.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.amplifyframework.ui.authenticator.ui - -import com.amplifyframework.auth.MFAType -import com.amplifyframework.ui.authenticator.ScreenshotTestBase -import com.amplifyframework.ui.authenticator.SignInContinueWithMfaSelectionState -import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep -import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldKey -import com.amplifyframework.ui.authenticator.mockFieldData -import com.amplifyframework.ui.authenticator.mockFieldState -import com.amplifyframework.ui.authenticator.mockForm -import org.junit.Test - -class SignInContinueWithMfaSelectionScreenshots : ScreenshotTestBase() { - - @Test - fun default_state() { - screenshot { - SignInContinueWithMfaSelection(mockSignInContinueWithMfaSelectionState()) - } - } - - private fun mockSignInContinueWithMfaSelectionState() = object : SignInContinueWithMfaSelectionState { - override val form = mockForm( - mockFieldData( - config = FieldConfig.Text(FieldKey.MfaSelection), - state = mockFieldState(content = "SMS_MFA") - ) - ) - override val allowedMfaTypes = MFAType.values().toSet() - override fun moveTo(step: AuthenticatorInitialStep) {} - override suspend fun continueSignIn() {} - override val step = AuthenticatorStep.SignInContinueWithMfaSelection - } -} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelection.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelection.kt deleted file mode 100644 index acc9b433..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelection.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.ui.authenticator.ui - -import com.amplifyframework.auth.MFAType -import com.amplifyframework.ui.authenticator.ScreenshotTestBase -import com.amplifyframework.ui.authenticator.SignInContinueWithMfaSetupSelectionState -import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep -import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldKey -import com.amplifyframework.ui.authenticator.mockFieldData -import com.amplifyframework.ui.authenticator.mockFieldState -import com.amplifyframework.ui.authenticator.mockForm -import org.junit.Test - -class SignInContinueWithMfaSetupSelection : ScreenshotTestBase() { - - @Test - fun default_state() { - screenshot { - SignInContinueWithMfaSetupSelection(mockSignInContinueWithMfaSetupSelectionState()) - } - } - - private fun mockSignInContinueWithMfaSetupSelectionState() = object : SignInContinueWithMfaSetupSelectionState { - override val form = mockForm( - mockFieldData( - config = FieldConfig.Text(FieldKey.MfaSelection), - state = mockFieldState(content = "EMAIL_OTP") - ) - ) - override val allowedMfaTypes: Set - get() = setOf(MFAType.TOTP, MFAType.SMS, MFAType.EMAIL) - - override fun moveTo(step: AuthenticatorInitialStep) {} - override suspend fun continueSignIn() {} - override val step = AuthenticatorStep.SignInContinueWithEmailSetup - } -} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpSetupScreenshots.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpSetupScreenshots.kt deleted file mode 100644 index cff46c92..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpSetupScreenshots.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.amplifyframework.ui.authenticator.ui - -import com.amplifyframework.ui.authenticator.ScreenshotTestBase -import com.amplifyframework.ui.authenticator.SignInContinueWithTotpSetupState -import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep -import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldError -import com.amplifyframework.ui.authenticator.forms.FieldKey -import com.amplifyframework.ui.authenticator.mockFieldData -import com.amplifyframework.ui.authenticator.mockFieldState -import com.amplifyframework.ui.authenticator.mockForm -import org.junit.Test - -class SignInContinueWithTotpSetupScreenshots : ScreenshotTestBase() { - - @Test - fun default_state() { - screenshot { - SignInContinueWithTotpSetup(state = mockSignInContinueWithTotpSetupState()) - } - } - - @Test - fun invalid_code() { - screenshot { - SignInContinueWithTotpSetup( - state = mockSignInContinueWithTotpSetupState( - confirmationCode = "123456", - fieldError = FieldError.ConfirmationCodeIncorrect - ) - ) - } - } - - private fun mockSignInContinueWithTotpSetupState( - confirmationCode: String = "", - sharedSecret: String = "secret", - setupUri: String = "https://docs.amplify.aws/android/tools/libraries/", - fieldError: FieldError? = null - ) = object : SignInContinueWithTotpSetupState { - override val form = mockForm( - mockFieldData( - config = FieldConfig.Text(FieldKey.ConfirmationCode), - state = mockFieldState(content = confirmationCode, error = fieldError) - ) - ) - override val sharedSecret = sharedSecret - override val setupUri = setupUri - override fun moveTo(step: AuthenticatorInitialStep) {} - override suspend fun continueSignIn() {} - override val step = AuthenticatorStep.SignInContinueWithTotpSetup - } -} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInScreenshots.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInScreenshots.kt deleted file mode 100644 index b4fe563f..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInScreenshots.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.ui.authenticator.ui - -import com.amplifyframework.ui.authenticator.ScreenshotTestBase -import com.amplifyframework.ui.authenticator.SignInState -import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep -import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldError -import com.amplifyframework.ui.authenticator.forms.FieldKey -import com.amplifyframework.ui.authenticator.mockFieldData -import com.amplifyframework.ui.authenticator.mockFieldState -import com.amplifyframework.ui.authenticator.mockForm -import com.amplifyframework.ui.authenticator.mockPasswordFieldState -import org.junit.Test - -class SignInScreenshots : ScreenshotTestBase() { - - @Test - fun default_state() { - screenshot { - SignIn(state = mockSignInState()) - } - } - - @Test - fun ready_to_submit() { - screenshot { - SignIn(state = mockSignInState(username = "username", password = "password")) - } - } - - @Test - fun password_visible() { - screenshot { - SignIn(state = mockSignInState(username = "username", password = "password", passwordVisible = true)) - } - } - - @Test - fun username_not_found() { - screenshot { - SignIn(state = mockSignInState(username = "username", usernameError = FieldError.NotFound)) - } - } - - private fun mockSignInState( - username: String = "", - password: String = "", - usernameError: FieldError? = null, - passwordVisible: Boolean = false - ) = object : SignInState { - override fun moveTo(step: AuthenticatorInitialStep) {} - override suspend fun signIn() {} - override val form = mockForm( - mockFieldData( - config = FieldConfig.Text(FieldKey.Username), - state = mockFieldState(content = username, error = usernameError) - ), - mockFieldData( - FieldConfig.Password(FieldKey.Password), - state = mockPasswordFieldState(content = password, visible = passwordVisible) - ) - ) - override val step = AuthenticatorStep.SignIn - } -} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpScreenshots.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpScreenshots.kt deleted file mode 100644 index 717a8c5c..00000000 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpScreenshots.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amplifyframework.ui.authenticator.ui - -import com.amplifyframework.ui.authenticator.ScreenshotTestBase -import com.amplifyframework.ui.authenticator.SignUpState -import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep -import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.forms.FieldConfig -import com.amplifyframework.ui.authenticator.forms.FieldError -import com.amplifyframework.ui.authenticator.forms.FieldKey -import com.amplifyframework.ui.authenticator.forms.PasswordError -import com.amplifyframework.ui.authenticator.mockFieldData -import com.amplifyframework.ui.authenticator.mockFieldState -import com.amplifyframework.ui.authenticator.mockForm -import com.amplifyframework.ui.authenticator.mockPasswordFieldState -import io.mockk.every -import io.mockk.mockk -import org.junit.Test - -class SignUpScreenshots : ScreenshotTestBase() { - - @Test - fun default_state() { - screenshot { - SignUp(state = mockSignUpState()) - } - } - - @Test - fun ready_to_submit() { - screenshot { - SignUp( - state = mockSignUpState( - username = "username", - password = "password", - confirmPassword = "password", - email = "email@email.com" - ) - ) - } - } - - @Test - fun password_visible() { - screenshot { - SignUp( - state = mockSignUpState( - username = "username", - password = "password", - confirmPassword = "password", - email = "email@email.com", - passwordVisible = true - ) - ) - } - } - - @Test - fun username_exists() { - screenshot { - SignUp( - state = mockSignUpState( - username = "username", - password = "password", - confirmPassword = "password", - email = "email@email.com", - usernameError = FieldError.FieldValueExists - ) - ) - } - } - - @Test - fun invalid_password() { - val error = mockk { - every { errors } returns listOf( - PasswordError.InvalidPasswordLength(10), - PasswordError.InvalidPasswordMissingUpper, - PasswordError.InvalidPasswordMissingSpecial, - PasswordError.InvalidPasswordMissingNumber - ) - } - - screenshot { - SignUp( - state = mockSignUpState( - username = "username", - password = "password", - confirmPassword = "password", - email = "email@email.com", - passwordError = error - ) - ) - } - } - - @Test - fun passwords_do_not_match() { - screenshot { - SignUp( - state = mockSignUpState( - username = "username", - password = "password", - confirmPassword = "password2", - email = "email@email.com", - confirmPasswordError = FieldError.PasswordsDoNotMatch - ) - ) - } - } - - @Test - fun invalid_email() { - screenshot { - SignUp( - state = mockSignUpState( - username = "username", - password = "password", - confirmPassword = "password2", - email = "email@email.com", - emailError = FieldError.InvalidFormat - ) - ) - } - } - - private fun mockSignUpState( - username: String = "", - password: String = "", - confirmPassword: String = "", - email: String = "", - usernameError: FieldError? = null, - passwordError: FieldError? = null, - confirmPasswordError: FieldError? = null, - emailError: FieldError? = null, - passwordVisible: Boolean = false - ) = object : SignUpState { - override fun moveTo(step: AuthenticatorInitialStep) {} - override suspend fun signUp() {} - override val form = mockForm( - mockFieldData( - config = FieldConfig.Text(FieldKey.Username), - state = mockFieldState(content = username, error = usernameError) - ), - mockFieldData( - config = FieldConfig.Password(FieldKey.Password), - state = mockPasswordFieldState(content = password, error = passwordError, visible = passwordVisible) - ), - mockFieldData( - config = FieldConfig.Password(FieldKey.ConfirmPassword), - state = mockPasswordFieldState( - content = confirmPassword, - error = confirmPasswordError, - visible = passwordVisible - ) - ), - mockFieldData( - config = FieldConfig.Text(FieldKey.Email), - state = mockFieldState(content = email, error = emailError) - ) - ) - override val step = AuthenticatorStep.SignUp - } -} diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_PasswordResetScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_PasswordResetScreenshots_default_state.png deleted file mode 100644 index 815f187d..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_PasswordResetScreenshots_default_state.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_PasswordResetScreenshots_username_not_found.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_PasswordResetScreenshots_username_not_found.png deleted file mode 100644 index 579fb7c2..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_PasswordResetScreenshots_username_not_found.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_default.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_default.png deleted file mode 100644 index 8ff47960..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_default.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_incorrect_code.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_incorrect_code.png deleted file mode 100644 index 858f28c4..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_incorrect_code.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_default.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_default.png deleted file mode 100644 index 2a6f2d18..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_default.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_incorrect_code.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_incorrect_code.png deleted file mode 100644 index aa3eaef8..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_incorrect_code.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_default_state.png deleted file mode 100644 index 8579db2f..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_default_state.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_invalid_code.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_invalid_code.png deleted file mode 100644 index b6fac089..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_invalid_code.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithEmailSetupScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithEmailSetupScreenshots_default_state.png deleted file mode 100644 index 7608db24..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithEmailSetupScreenshots_default_state.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSelectionScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSelectionScreenshots_default_state.png deleted file mode 100644 index 0d58a407..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSelectionScreenshots_default_state.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSetupSelection_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSetupSelection_default_state.png deleted file mode 100644 index 7c5bcf25..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSetupSelection_default_state.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_default_state.png deleted file mode 100644 index f7f7d8c9..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_default_state.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_invalid_code.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_invalid_code.png deleted file mode 100644 index 91b362b4..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_invalid_code.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_default_state.png deleted file mode 100644 index b4e35cb7..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_default_state.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_password_visible.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_password_visible.png deleted file mode 100644 index d0e3ebbc..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_password_visible.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_ready_to_submit.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_ready_to_submit.png deleted file mode 100644 index 23c0f8a2..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_ready_to_submit.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_username_not_found.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_username_not_found.png deleted file mode 100644 index 3b917146..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInScreenshots_username_not_found.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_default_state.png deleted file mode 100644 index cd3d4523..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_default_state.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_invalid_email.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_invalid_email.png deleted file mode 100644 index ecea9c30..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_invalid_email.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_invalid_password.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_invalid_password.png deleted file mode 100644 index 5cddfdf7..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_invalid_password.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_password_visible.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_password_visible.png deleted file mode 100644 index fa5564eb..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_password_visible.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_passwords_do_not_match.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_passwords_do_not_match.png deleted file mode 100644 index d1e8aa24..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_passwords_do_not_match.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_ready_to_submit.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_ready_to_submit.png deleted file mode 100644 index b6063a86..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_ready_to_submit.png and /dev/null differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_username_exists.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_username_exists.png deleted file mode 100644 index 0c128822..00000000 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignUpScreenshots_username_exists.png and /dev/null differ diff --git a/authenticator/gradle.properties b/authenticator/gradle.properties index 270e0f28..cab47cce 100644 --- a/authenticator/gradle.properties +++ b/authenticator/gradle.properties @@ -17,4 +17,4 @@ POM_ARTIFACT_ID=authenticator POM_NAME=Amplify UI Framework for Android - Authenticator POM_DESCRIPTION=Amplify UI Framework for Android - Authenticator Plugin POM_PACKAGING=aar -VERSION_NAME=1.5.0 +VERSION_NAME=1.5.0 \ No newline at end of file diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorTitle.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorTitle.kt index d9abb448..a44b31cd 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorTitle.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorTitle.kt @@ -34,6 +34,6 @@ internal fun AuthenticatorTitle( Text( style = MaterialTheme.typography.titleLarge, text = text, - modifier = modifier.padding(bottom = 16.dp).testTag("AuthenticatorTitle") + modifier = modifier.padding(bottom = 16.dp).testTag(TestTags.AuthenticatorTitle) ) } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordInputField.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordInputField.kt index 19065e38..8483a41a 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordInputField.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordInputField.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction @@ -91,7 +92,7 @@ private fun getTrailingIcon(visible: Boolean, onClick: () -> Unit): @Composable true -> R.string.amplify_ui_authenticator_field_a11y_password_hide false -> R.string.amplify_ui_authenticator_field_a11y_password_show } - IconButton(onClick = onClick) { + IconButton(onClick = onClick, modifier = Modifier.testTag(TestTags.ShowPasswordIcon)) { Icon(painter = painterResource(icon), contentDescription = stringResource(contentDescription)) } } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordReset.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordReset.kt index 74168b50..1de1da1c 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordReset.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordReset.kt @@ -53,7 +53,7 @@ fun PasswordReset( state = state.form ) AuthenticatorButton( - modifier = Modifier.testTag("PasswordResetButton"), + modifier = Modifier.testTag(TestTags.PasswordResetButton), onClick = { scope.launch { state.submitPasswordReset() } }, loading = !state.form.enabled ) diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignIn.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignIn.kt index dc2c98da..35f1ade0 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignIn.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignIn.kt @@ -54,7 +54,7 @@ fun SignIn( onClick = { scope.launch { state.signIn() } }, loading = !state.form.enabled, label = stringResource(R.string.amplify_ui_authenticator_button_signin), - modifier = Modifier.testTag("SignInButton") + modifier = Modifier.testTag(TestTags.SignInButton) ) footerContent(state) } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignUp.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignUp.kt index 22e60fd3..d6aeb0a2 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignUp.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignUp.kt @@ -52,7 +52,7 @@ fun SignUp( onClick = { scope.launch { state.signUp() } }, loading = !state.form.enabled, label = stringResource(R.string.amplify_ui_authenticator_button_signup), - modifier = Modifier.testTag("SignUpButton") + modifier = Modifier.testTag(TestTags.SignUpButton) ) footerContent(state) } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt index 6af214c3..208195ae 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt @@ -19,4 +19,10 @@ internal object TestTags { const val SignInConfirmButton = "SignInConfirmButton" const val BackToSignInButton = "BackToSignInButton" const val CopyKeyButton = "CopyKeyButton" + const val SignInButton = "SignInButton" + const val SignUpButton = "SignUpButton" + const val PasswordResetButton = "PasswordResetButton" + const val AuthenticatorTitle = "AuthenticatorTitle" + + const val ShowPasswordIcon = "ShowPasswordIcon" } diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ScreenshotTestBase.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/AuthenticatorUiTest.kt similarity index 55% rename from authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ScreenshotTestBase.kt rename to authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/AuthenticatorUiTest.kt index bc673736..670dda89 100644 --- a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ScreenshotTestBase.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/AuthenticatorUiTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,35 +13,24 @@ * permissions and limitations under the License. */ -package com.amplifyframework.ui.authenticator +package com.amplifyframework.ui.authenticator.testUtil import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import app.cash.paparazzi.DeviceConfig -import app.cash.paparazzi.Paparazzi import com.amplifyframework.ui.authenticator.theme.AmplifyTheme -import org.junit.Rule +import com.amplifyframework.ui.testing.ComposeTest -abstract class ScreenshotTestBase { - - @get:Rule - val screenshotRule = Paparazzi( - deviceConfig = DeviceConfig.PIXEL_6, - showSystemUi = false - ) - - protected open fun screenshot(name: String? = null, content: @Composable () -> Unit) = - screenshotRule.snapshot(name) { +abstract class AuthenticatorUiTest : ComposeTest() { + override fun setContent(content: @Composable () -> Unit) { + super.setContent { AmplifyTheme { - Surface { - Box(modifier = Modifier.padding(top = 56.dp)) { - content() - } + Box(modifier = Modifier.padding(top = 16.dp)) { + content() } } } + } } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt new file mode 100644 index 00000000..e980edd2 --- /dev/null +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amplifyframework.ui.authenticator.testUtil + +import com.amplifyframework.auth.AuthCodeDeliveryDetails +import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.auth.MFAType +import com.amplifyframework.ui.authenticator.auth.PasswordCriteria +import com.amplifyframework.ui.authenticator.auth.SignInMethod +import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep +import com.amplifyframework.ui.authenticator.forms.FormData +import com.amplifyframework.ui.authenticator.states.PasswordResetStateImpl +import com.amplifyframework.ui.authenticator.states.SignInConfirmMfaStateImpl +import com.amplifyframework.ui.authenticator.states.SignInConfirmTotpCodeStateImpl +import com.amplifyframework.ui.authenticator.states.SignInContinueWithEmailSetupStateImpl +import com.amplifyframework.ui.authenticator.states.SignInContinueWithMfaSelectionStateImpl +import com.amplifyframework.ui.authenticator.states.SignInContinueWithMfaSetupSelectionStateImpl +import com.amplifyframework.ui.authenticator.states.SignInContinueWithTotpSetupStateImpl +import com.amplifyframework.ui.authenticator.states.SignInStateImpl +import com.amplifyframework.ui.authenticator.states.SignUpStateImpl + +internal fun mockSignInState() = SignInStateImpl( + signInMethod = SignInMethod.Username, + onSubmit = { _, _ -> }, + onMoveTo = { } +) + +internal fun mockSignUpState() = SignUpStateImpl( + signInMethod = SignInMethod.Username, + signUpAttributes = listOf(AuthUserAttributeKey.email()), + passwordCriteria = PasswordCriteria(8, false, false, false, false), + signUpForm = FormData(emptyList()), + onSubmit = { _, _, _ -> }, + onMoveTo = { } +) + +internal fun mockPasswordResetState() = PasswordResetStateImpl( + signInMethod = SignInMethod.Username, + onSubmit = {}, + onMoveTo = {} +) + +internal fun mockSignInConfirmTotpCodeState( + onSubmit: (String) -> Unit = { }, + onMoveTo: (AuthenticatorInitialStep) -> Unit = { } +) = SignInConfirmTotpCodeStateImpl( + onSubmit = onSubmit, + onMoveTo = onMoveTo +) + +internal fun mockSignInContinueWithEmailSetupState( + onSubmit: suspend (email: String) -> Unit = {}, + onMoveTo: (step: AuthenticatorInitialStep) -> Unit = {} +) = SignInContinueWithEmailSetupStateImpl( + onSubmit = onSubmit, + onMoveTo = onMoveTo +) + +internal fun mockSignInContinueWithMfaSelectionState( + allowedMfaTypes: Set = MFAType.entries.toSet(), + onSubmit: (String) -> Unit = {}, + onMoveTo: (AuthenticatorInitialStep) -> Unit = {} +) = SignInContinueWithMfaSelectionStateImpl( + allowedMfaTypes = allowedMfaTypes, + onSubmit = onSubmit, + onMoveTo = onMoveTo +) + +internal fun mockSignInContinueWithTotpSetupState( + sharedSecret: String = "", + setupUri: String = "", + onSubmit: (String) -> Unit = { }, + onMoveTo: (AuthenticatorInitialStep) -> Unit = { } +) = SignInContinueWithTotpSetupStateImpl( + sharedSecret = sharedSecret, + setupUri = setupUri, + onSubmit = onSubmit, + onMoveTo = onMoveTo +) + +internal fun mockSignInConfirmMfaState( + deliveryDetails: AuthCodeDeliveryDetails = AuthCodeDeliveryDetails( + "123-123-1234", + AuthCodeDeliveryDetails.DeliveryMedium.SMS + ) +) = SignInConfirmMfaStateImpl( + deliveryDetails = deliveryDetails, + onSubmit = { }, + onMoveTo = { } +) + +internal fun mockSignInContinueWithMfaSetupSelectionState( + allowedMfaTypes: Set = setOf(MFAType.TOTP, MFAType.SMS, MFAType.EMAIL) +) = SignInContinueWithMfaSetupSelectionStateImpl( + allowedMfaTypes = allowedMfaTypes, + onSubmit = { }, + onMoveTo = { } +) diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/theme/Color.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/theme/Color.kt similarity index 100% rename from authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/theme/Color.kt rename to authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/theme/Color.kt diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/theme/Theme.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/theme/Theme.kt similarity index 100% rename from authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/theme/Theme.kt rename to authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/theme/Theme.kt diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetTest.kt index 651da8f8..406e6681 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetTest.kt @@ -15,13 +15,16 @@ package com.amplifyframework.ui.authenticator.ui -import com.amplifyframework.ui.authenticator.auth.SignInMethod -import com.amplifyframework.ui.authenticator.states.PasswordResetStateImpl +import com.amplifyframework.ui.authenticator.forms.FieldError +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.forms.setFieldError +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockPasswordResetState import com.amplifyframework.ui.authenticator.ui.robots.passwordReset -import com.amplifyframework.ui.testing.ComposeTest +import com.amplifyframework.ui.testing.ScreenshotTest import org.junit.Test -class PasswordResetTest : ComposeTest() { +class PasswordResetTest : AuthenticatorUiTest() { @Test fun `title is reset password`() { @@ -53,9 +56,24 @@ class PasswordResetTest : ComposeTest() { } } - private fun mockPasswordResetState() = PasswordResetStateImpl( - signInMethod = SignInMethod.Username, - onSubmit = {}, - onMoveTo = {} - ) + @Test + @ScreenshotTest + fun `default state`() { + setContent { + PasswordReset(state = mockPasswordResetState()) + } + } + + @Test + @ScreenshotTest + fun `username not found`() { + val state = mockPasswordResetState() + setContent { + PasswordReset(state = state) + } + passwordReset { + setUsername("username") + } + state.form.setFieldError(FieldKey.Username, FieldError.NotFound) + } } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmMfaTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmMfaTest.kt new file mode 100644 index 00000000..137b89bc --- /dev/null +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmMfaTest.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amplifyframework.ui.authenticator.ui + +import com.amplifyframework.ui.authenticator.forms.FieldError +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.forms.setFieldError +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockSignInConfirmMfaState +import com.amplifyframework.ui.authenticator.ui.robots.signInConfirmMfa +import com.amplifyframework.ui.testing.ScreenshotTest +import org.junit.Test + +class SignInConfirmMfaTest : AuthenticatorUiTest() { + @Test + @ScreenshotTest + fun `default state`() { + setContent { + SignInConfirmMfa( + mockSignInConfirmMfaState() + ) + } + } + + @Test + @ScreenshotTest + fun `incorrect code`() { + val state = mockSignInConfirmMfaState() + setContent { + SignInConfirmMfa(state) + } + + signInConfirmMfa { + setConfirmationCode("123456") + } + + state.form.setFieldError(FieldKey.ConfirmationCode, FieldError.ConfirmationCodeIncorrect) + } +} diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmTotpCodeTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmTotpCodeTest.kt index 853d3552..5645d3d7 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmTotpCodeTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmTotpCodeTest.kt @@ -17,14 +17,18 @@ package com.amplifyframework.ui.authenticator.ui import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.states.SignInConfirmTotpCodeStateImpl +import com.amplifyframework.ui.authenticator.forms.FieldError +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.forms.setFieldError +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockSignInConfirmTotpCodeState import com.amplifyframework.ui.authenticator.ui.robots.signInConfirmTotpCode -import com.amplifyframework.ui.testing.ComposeTest +import com.amplifyframework.ui.testing.ScreenshotTest import io.mockk.mockk import io.mockk.verify import org.junit.Test -class SignInConfirmTotpCodeTest : ComposeTest() { +class SignInConfirmTotpCodeTest : AuthenticatorUiTest() { @Test fun `title is Enter your one-time passcode`() { setContent { @@ -76,11 +80,24 @@ class SignInConfirmTotpCodeTest : ComposeTest() { } } - private fun mockSignInConfirmTotpCodeState( - onSubmit: (String) -> Unit = { }, - onMoveTo: (AuthenticatorInitialStep) -> Unit = { } - ) = SignInConfirmTotpCodeStateImpl( - onSubmit = onSubmit, - onMoveTo = onMoveTo - ) + @Test + @ScreenshotTest + fun `default state`() { + setContent { + SignInConfirmTotpCode(state = mockSignInConfirmTotpCodeState()) + } + } + + @Test + @ScreenshotTest + fun `invalid code`() { + val state = mockSignInConfirmTotpCodeState() + setContent { + SignInConfirmTotpCode(state = state) + } + signInConfirmTotpCode { + setConfirmationCode("123456") + } + state.form.setFieldError(FieldKey.ConfirmationCode, FieldError.ConfirmationCodeIncorrect) + } } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupTest.kt index 29b025b1..def821a4 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupTest.kt @@ -17,14 +17,15 @@ package com.amplifyframework.ui.authenticator.ui import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.states.SignInContinueWithEmailSetupStateImpl +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockSignInContinueWithEmailSetupState import com.amplifyframework.ui.authenticator.ui.robots.signInContinueWithEmailSetup -import com.amplifyframework.ui.testing.ComposeTest +import com.amplifyframework.ui.testing.ScreenshotTest import io.mockk.mockk import io.mockk.verify import org.junit.Test -class SignInContinueWithEmailSetupTest : ComposeTest() { +class SignInContinueWithEmailSetupTest : AuthenticatorUiTest() { @Test fun `title is Setup Two-Factor Auth Method`() { @@ -81,11 +82,11 @@ class SignInContinueWithEmailSetupTest : ComposeTest() { } } - private fun mockSignInContinueWithEmailSetupState( - onSubmit: suspend (email: String) -> Unit = {}, - onMoveTo: (step: AuthenticatorInitialStep) -> Unit = {} - ) = SignInContinueWithEmailSetupStateImpl( - onSubmit = onSubmit, - onMoveTo = onMoveTo - ) + @Test + @ScreenshotTest + fun `default state`() { + setContent { + SignInContinueWithEmailSetup(mockSignInContinueWithEmailSetupState()) + } + } } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionTest.kt index 9cd7e382..8c804241 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionTest.kt @@ -4,14 +4,15 @@ import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.cognito.challengeResponse import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.states.SignInContinueWithMfaSelectionStateImpl +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockSignInContinueWithMfaSelectionState import com.amplifyframework.ui.authenticator.ui.robots.signInContinueWithMfaSelection -import com.amplifyframework.ui.testing.ComposeTest +import com.amplifyframework.ui.testing.ScreenshotTest import io.mockk.mockk import io.mockk.verify import org.junit.Test -class SignInContinueWithMfaSelectionTest : ComposeTest() { +class SignInContinueWithMfaSelectionTest : AuthenticatorUiTest() { @Test fun `title is Choose your two-factor authentication method`() { @@ -106,13 +107,11 @@ class SignInContinueWithMfaSelectionTest : ComposeTest() { } } - private fun mockSignInContinueWithMfaSelectionState( - allowedMfaTypes: Set = MFAType.values().toSet(), - onSubmit: (String) -> Unit = {}, - onMoveTo: (AuthenticatorInitialStep) -> Unit = {} - ) = SignInContinueWithMfaSelectionStateImpl( - allowedMfaTypes = allowedMfaTypes, - onSubmit = onSubmit, - onMoveTo = onMoveTo - ) + @Test + @ScreenshotTest + fun `default state`() { + setContent { + SignInContinueWithMfaSelection(mockSignInContinueWithMfaSelectionState()) + } + } } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelectionTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelectionTest.kt new file mode 100644 index 00000000..53e99bab --- /dev/null +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelectionTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amplifyframework.ui.authenticator.ui + +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockSignInContinueWithMfaSetupSelectionState +import org.junit.Test + +class SignInContinueWithMfaSetupSelectionTest : AuthenticatorUiTest() { + @Test + fun `default state`() { + setContent { + SignInContinueWithMfaSetupSelection(mockSignInContinueWithMfaSetupSelectionState()) + } + } +} diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpCodeTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpSetupTest.kt similarity index 84% rename from authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpCodeTest.kt rename to authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpSetupTest.kt index 36a9d0f3..b4fc59ab 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpCodeTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpSetupTest.kt @@ -19,16 +19,17 @@ import android.content.ClipboardManager import android.content.Context import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.states.SignInContinueWithTotpSetupStateImpl +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockSignInContinueWithTotpSetupState import com.amplifyframework.ui.authenticator.ui.robots.signInContinueWithTotpSetup -import com.amplifyframework.ui.testing.ComposeTest +import com.amplifyframework.ui.testing.ScreenshotTest import io.kotest.matchers.shouldBe import io.mockk.mockk import io.mockk.verify import org.junit.Test import org.robolectric.RuntimeEnvironment -class SignInContinueWithTotpCodeTest : ComposeTest() { +class SignInContinueWithTotpSetupTest : AuthenticatorUiTest() { @Test fun `title is Enable Two-Factor Auth`() { setContent { @@ -92,17 +93,13 @@ class SignInContinueWithTotpCodeTest : ComposeTest() { getClipboardContent() shouldBe "secret!" } - private fun mockSignInContinueWithTotpSetupState( - sharedSecret: String = "", - setupUri: String = "", - onSubmit: (String) -> Unit = { }, - onMoveTo: (AuthenticatorInitialStep) -> Unit = { } - ) = SignInContinueWithTotpSetupStateImpl( - sharedSecret = sharedSecret, - setupUri = setupUri, - onSubmit = onSubmit, - onMoveTo = onMoveTo - ) + @Test + @ScreenshotTest + fun `default state`() { + setContent { + SignInContinueWithTotpSetup(state = mockSignInContinueWithTotpSetupState()) + } + } private fun getClipboardContent(): String? { val clipboardManager = diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInTest.kt index a588be8e..612d0140 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInTest.kt @@ -15,13 +15,16 @@ package com.amplifyframework.ui.authenticator.ui -import com.amplifyframework.ui.authenticator.auth.SignInMethod -import com.amplifyframework.ui.authenticator.states.SignInStateImpl +import com.amplifyframework.ui.authenticator.forms.FieldError +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.forms.setFieldError +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockSignInState import com.amplifyframework.ui.authenticator.ui.robots.signIn -import com.amplifyframework.ui.testing.ComposeTest +import com.amplifyframework.ui.testing.ScreenshotTest import org.junit.Test -class SignInTest : ComposeTest() { +class SignInTest : AuthenticatorUiTest() { @Test fun `title is Sign In`() { @@ -43,9 +46,49 @@ class SignInTest : ComposeTest() { } } - private fun mockSignInState() = SignInStateImpl( - signInMethod = SignInMethod.Username, - onSubmit = { _, _ -> }, - onMoveTo = { } - ) + @Test + @ScreenshotTest + fun `default state`() { + setContent { + SignIn(state = mockSignInState()) + } + } + + @Test + @ScreenshotTest + fun `ready to submit`() { + setContent { + SignIn(state = mockSignInState()) + } + signIn { + setUsername("username") + setPassword("password") + } + } + + @Test + @ScreenshotTest + fun `password visible`() { + setContent { + SignIn(state = mockSignInState()) + } + signIn { + setUsername("username") + setPassword("password") + clickShowPassword() + } + } + + @Test + @ScreenshotTest + fun `username not found`() { + val state = mockSignInState() + setContent { + SignIn(state = state) + } + signIn { + setUsername("username") + } + state.form.setFieldError(FieldKey.Username, FieldError.NotFound) + } } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt index 1c9c8b49..e9fe1156 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt @@ -15,15 +15,17 @@ package com.amplifyframework.ui.authenticator.ui -import com.amplifyframework.ui.authenticator.auth.PasswordCriteria -import com.amplifyframework.ui.authenticator.auth.SignInMethod -import com.amplifyframework.ui.authenticator.forms.FormData -import com.amplifyframework.ui.authenticator.states.SignUpStateImpl +import com.amplifyframework.ui.authenticator.forms.FieldError +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.forms.PasswordError +import com.amplifyframework.ui.authenticator.forms.setFieldError +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockSignUpState import com.amplifyframework.ui.authenticator.ui.robots.signUp -import com.amplifyframework.ui.testing.ComposeTest +import com.amplifyframework.ui.testing.ScreenshotTest import org.junit.Test -class SignUpTest : ComposeTest() { +class SignUpTest : AuthenticatorUiTest() { @Test fun `title is Create Account`() { @@ -45,12 +47,117 @@ class SignUpTest : ComposeTest() { } } - private fun mockSignUpState() = SignUpStateImpl( - signInMethod = SignInMethod.Username, - signUpAttributes = emptyList(), - passwordCriteria = PasswordCriteria(8, false, false, false, false), - signUpForm = FormData(emptyList()), - onSubmit = { _, _, _ -> }, - onMoveTo = { } - ) + @Test + @ScreenshotTest + fun `default state`() { + setContent { + SignUp(state = mockSignUpState()) + } + } + + @Test + @ScreenshotTest + fun `ready to submit`() { + setContent { + SignUp(state = mockSignUpState()) + } + signUp { + setUsername("username") + setPassword("password") + setConfirmPassword("password") + setEmail("email@email.com") + } + } + + @Test + @ScreenshotTest + fun `password visible`() { + setContent { + SignUp(state = mockSignUpState()) + } + signUp { + setUsername("username") + setPassword("password") + setConfirmPassword("password") + setEmail("email@email.com") + + clickShowPassword(FieldKey.Password) + clickShowPassword(FieldKey.ConfirmPassword) + } + } + + @Test + @ScreenshotTest + fun `username exists`() { + val state = mockSignUpState() + setContent { + SignUp(state = state) + } + signUp { + setUsername("username") + setPassword("password") + setConfirmPassword("password") + setEmail("email@email.com") + } + + state.form.setFieldError(FieldKey.Username, FieldError.FieldValueExists) + } + + @Test + @ScreenshotTest + fun `invalid password`() { + val state = mockSignUpState() + setContent { + SignUp(state = state) + } + signUp { + setUsername("username") + setPassword("password") + setConfirmPassword("password") + setEmail("email@email.com") + } + + val error = FieldError.InvalidPassword( + listOf( + PasswordError.InvalidPasswordLength(10), + PasswordError.InvalidPasswordMissingUpper, + PasswordError.InvalidPasswordMissingSpecial, + PasswordError.InvalidPasswordMissingNumber + ) + ) + + state.form.setFieldError(FieldKey.Password, error) + } + + @Test + @ScreenshotTest + fun `passwords do not match`() { + val state = mockSignUpState() + setContent { + SignUp(state = state) + } + signUp { + setUsername("username") + setPassword("password") + setConfirmPassword("password") + setEmail("email@email.com") + } + state.form.setFieldError(FieldKey.ConfirmPassword, FieldError.PasswordsDoNotMatch) + } + + @Test + @ScreenshotTest + fun `invalid email`() { + val state = mockSignUpState() + setContent { + SignUp(state = state) + } + signUp { + setUsername("username") + setPassword("password") + setConfirmPassword("password") + setEmail("email@email.com") + } + state.form.setFieldError(FieldKey.Email, FieldError.InvalidFormat) + } } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/PasswordResetRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/PasswordResetRobot.kt index 9819518f..48330d36 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/PasswordResetRobot.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/PasswordResetRobot.kt @@ -16,11 +16,14 @@ package com.amplifyframework.ui.authenticator.ui.robots import androidx.compose.ui.test.junit4.ComposeTestRule +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.ui.TestTags import com.amplifyframework.ui.testing.ComposeTest fun ComposeTest.passwordReset(func: PasswordResetRobot.() -> Unit) = PasswordResetRobot(composeTestRule).func() class PasswordResetRobot(rule: ComposeTestRule) : ScreenLevelRobot(rule) { + fun hasSubmitButton(expected: String) = assertExists(TestTags.PasswordResetButton, expected) - fun hasSubmitButton(expected: String) = assertExists("PasswordResetButton", expected) + fun setUsername(value: String) = setFieldContent(FieldKey.Username, value) } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/ScreenLevelRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/ScreenLevelRobot.kt index 8c32d706..db48f964 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/ScreenLevelRobot.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/ScreenLevelRobot.kt @@ -15,13 +15,21 @@ package com.amplifyframework.ui.authenticator.ui.robots +import androidx.compose.ui.test.hasAnyAncestor +import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.performClick import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.ui.TestTags import com.amplifyframework.ui.testing.ComposeRobot abstract class ScreenLevelRobot(rule: ComposeTestRule) : ComposeRobot(rule) { // Check that the composable has the expected title - fun hasTitle(expected: String) = assertExists("AuthenticatorTitle", expected) + fun hasTitle(expected: String) = assertExists(TestTags.AuthenticatorTitle, expected) fun setFieldContent(key: FieldKey, content: String) = writeTo(key.toString(), content) + + fun clickOnShowIcon(key: FieldKey) = composeTestRule.onNode( + hasTestTag(TestTags.ShowPasswordIcon) and hasAnyAncestor(hasTestTag(key.toString())) + ).performClick() } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInConfirmMfaRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInConfirmMfaRobot.kt new file mode 100644 index 00000000..a9978757 --- /dev/null +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInConfirmMfaRobot.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amplifyframework.ui.authenticator.ui.robots + +import androidx.compose.ui.test.junit4.ComposeTestRule +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.testing.ComposeTest + +fun ComposeTest.signInConfirmMfa(func: SignInConfirmMfaRobot.() -> Unit) = SignInConfirmMfaRobot( + composeTestRule +).func() + +class SignInConfirmMfaRobot(composeTestRule: ComposeTestRule) : ScreenLevelRobot(composeTestRule) { + + fun setConfirmationCode(value: String) = setFieldContent(FieldKey.ConfirmationCode, value) +} diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInRobot.kt index 4a7daef9..0555bad3 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInRobot.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInRobot.kt @@ -16,10 +16,15 @@ package com.amplifyframework.ui.authenticator.ui.robots import androidx.compose.ui.test.junit4.ComposeTestRule +import com.amplifyframework.ui.authenticator.forms.FieldKey import com.amplifyframework.ui.testing.ComposeTest fun ComposeTest.signIn(func: SignInRobot.() -> Unit) = SignInRobot(composeTestRule).func() class SignInRobot(rule: ComposeTestRule) : ScreenLevelRobot(rule) { fun hasSubmitButton(expected: String) = assertExists("SignInButton", expected) + + fun setUsername(value: String) = setFieldContent(FieldKey.Username, value) + fun setPassword(value: String) = setFieldContent(FieldKey.Password, value) + fun clickShowPassword() = clickOnShowIcon(FieldKey.Password) } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignUpRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignUpRobot.kt index 5318f39c..d7774645 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignUpRobot.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignUpRobot.kt @@ -16,10 +16,18 @@ package com.amplifyframework.ui.authenticator.ui.robots import androidx.compose.ui.test.junit4.ComposeTestRule +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.ui.TestTags import com.amplifyframework.ui.testing.ComposeTest fun ComposeTest.signUp(func: SignUpRobot.() -> Unit) = SignUpRobot(composeTestRule).func() class SignUpRobot(rule: ComposeTestRule) : ScreenLevelRobot(rule) { - fun hasSubmitButton(expected: String) = assertExists("SignUpButton", expected) + fun hasSubmitButton(expected: String) = assertExists(TestTags.SignUpButton, expected) + + fun setUsername(value: String) = setFieldContent(FieldKey.Username, value) + fun setPassword(value: String) = setFieldContent(FieldKey.Password, value) + fun setConfirmPassword(value: String) = setFieldContent(FieldKey.ConfirmPassword, value) + fun setEmail(value: String) = setFieldContent(FieldKey.Email, value) + fun clickShowPassword(fieldKey: FieldKey) = clickOnShowIcon(fieldKey) } diff --git a/authenticator/src/test/screenshots/PasswordResetTest_default-state.png b/authenticator/src/test/screenshots/PasswordResetTest_default-state.png new file mode 100644 index 00000000..99f3aea3 Binary files /dev/null and b/authenticator/src/test/screenshots/PasswordResetTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/PasswordResetTest_username-not-found.png b/authenticator/src/test/screenshots/PasswordResetTest_username-not-found.png new file mode 100644 index 00000000..865a92b8 Binary files /dev/null and b/authenticator/src/test/screenshots/PasswordResetTest_username-not-found.png differ diff --git a/authenticator/src/test/screenshots/SignInConfirmMfaTest_default-state.png b/authenticator/src/test/screenshots/SignInConfirmMfaTest_default-state.png new file mode 100644 index 00000000..ef5a1f93 Binary files /dev/null and b/authenticator/src/test/screenshots/SignInConfirmMfaTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInConfirmMfaTest_incorrect-code.png b/authenticator/src/test/screenshots/SignInConfirmMfaTest_incorrect-code.png new file mode 100644 index 00000000..94165b5c Binary files /dev/null and b/authenticator/src/test/screenshots/SignInConfirmMfaTest_incorrect-code.png differ diff --git a/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_default-state.png b/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_default-state.png new file mode 100644 index 00000000..c72ff482 Binary files /dev/null and b/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_invalid-code.png b/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_invalid-code.png new file mode 100644 index 00000000..5e903b17 Binary files /dev/null and b/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_invalid-code.png differ diff --git a/authenticator/src/test/screenshots/SignInContinueWithEmailSetupTest_default-state.png b/authenticator/src/test/screenshots/SignInContinueWithEmailSetupTest_default-state.png new file mode 100644 index 00000000..f3f66e19 Binary files /dev/null and b/authenticator/src/test/screenshots/SignInContinueWithEmailSetupTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInContinueWithMfaSelectionTest_default-state.png b/authenticator/src/test/screenshots/SignInContinueWithMfaSelectionTest_default-state.png new file mode 100644 index 00000000..a5f5602d Binary files /dev/null and b/authenticator/src/test/screenshots/SignInContinueWithMfaSelectionTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInContinueWithTotpSetupTest_default-state.png b/authenticator/src/test/screenshots/SignInContinueWithTotpSetupTest_default-state.png new file mode 100644 index 00000000..1081a32f Binary files /dev/null and b/authenticator/src/test/screenshots/SignInContinueWithTotpSetupTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInTest_default-state.png b/authenticator/src/test/screenshots/SignInTest_default-state.png new file mode 100644 index 00000000..9f4ccc9a Binary files /dev/null and b/authenticator/src/test/screenshots/SignInTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInTest_password-visible.png b/authenticator/src/test/screenshots/SignInTest_password-visible.png new file mode 100644 index 00000000..fa61261d Binary files /dev/null and b/authenticator/src/test/screenshots/SignInTest_password-visible.png differ diff --git a/authenticator/src/test/screenshots/SignInTest_ready-to-submit.png b/authenticator/src/test/screenshots/SignInTest_ready-to-submit.png new file mode 100644 index 00000000..9df29da5 Binary files /dev/null and b/authenticator/src/test/screenshots/SignInTest_ready-to-submit.png differ diff --git a/authenticator/src/test/screenshots/SignInTest_username-not-found.png b/authenticator/src/test/screenshots/SignInTest_username-not-found.png new file mode 100644 index 00000000..b192482f Binary files /dev/null and b/authenticator/src/test/screenshots/SignInTest_username-not-found.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_default-state.png b/authenticator/src/test/screenshots/SignUpTest_default-state.png new file mode 100644 index 00000000..c4419737 Binary files /dev/null and b/authenticator/src/test/screenshots/SignUpTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_invalid-email.png b/authenticator/src/test/screenshots/SignUpTest_invalid-email.png new file mode 100644 index 00000000..baf6ffbc Binary files /dev/null and b/authenticator/src/test/screenshots/SignUpTest_invalid-email.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_invalid-password.png b/authenticator/src/test/screenshots/SignUpTest_invalid-password.png new file mode 100644 index 00000000..8b6364b6 Binary files /dev/null and b/authenticator/src/test/screenshots/SignUpTest_invalid-password.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_password-visible.png b/authenticator/src/test/screenshots/SignUpTest_password-visible.png new file mode 100644 index 00000000..35f465de Binary files /dev/null and b/authenticator/src/test/screenshots/SignUpTest_password-visible.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_passwords-do-not-match.png b/authenticator/src/test/screenshots/SignUpTest_passwords-do-not-match.png new file mode 100644 index 00000000..d2c08a28 Binary files /dev/null and b/authenticator/src/test/screenshots/SignUpTest_passwords-do-not-match.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_ready-to-submit.png b/authenticator/src/test/screenshots/SignUpTest_ready-to-submit.png new file mode 100644 index 00000000..ad1bc4ec Binary files /dev/null and b/authenticator/src/test/screenshots/SignUpTest_ready-to-submit.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_username-exists.png b/authenticator/src/test/screenshots/SignUpTest_username-exists.png new file mode 100644 index 00000000..9f6fbcec Binary files /dev/null and b/authenticator/src/test/screenshots/SignUpTest_username-exists.png differ diff --git a/build-logic/plugins/build.gradle.kts b/build-logic/plugins/build.gradle.kts index 08e13de2..993f79f1 100644 --- a/build-logic/plugins/build.gradle.kts +++ b/build-logic/plugins/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.ir.backend.js.compile + /* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * @@ -38,6 +40,7 @@ dependencies { compileOnly(libs.plugin.kover) compileOnly(libs.plugin.ktlint) compileOnly(libs.plugin.licensee) + compileOnly(libs.plugin.roborazzi) } gradlePlugin { @@ -70,5 +73,9 @@ gradlePlugin { id = "amplify.android.publishing" implementationClass = "PublishingConventionPlugin" } + register("screenshots") { + id = "amplify.android.screenshots" + implementationClass = "ScreenshotConventionPlugin" + } } } diff --git a/build-logic/plugins/src/main/kotlin/ComponentConventionPlugin.kt b/build-logic/plugins/src/main/kotlin/ComponentConventionPlugin.kt index 69218eae..72c0173c 100644 --- a/build-logic/plugins/src/main/kotlin/ComponentConventionPlugin.kt +++ b/build-logic/plugins/src/main/kotlin/ComponentConventionPlugin.kt @@ -34,6 +34,7 @@ class ComponentConventionPlugin : Plugin { pluginManager.apply("amplify.android.kover") pluginManager.apply("amplify.android.api.validator") pluginManager.apply("amplify.android.licenses") + pluginManager.apply("amplify.android.screenshots") tasks.withType().configureEach { kotlinOptions { diff --git a/build-logic/plugins/src/main/kotlin/ScreenshotConventionPlugin.kt b/build-logic/plugins/src/main/kotlin/ScreenshotConventionPlugin.kt new file mode 100644 index 00000000..66ed48f6 --- /dev/null +++ b/build-logic/plugins/src/main/kotlin/ScreenshotConventionPlugin.kt @@ -0,0 +1,17 @@ +import io.github.takahirom.roborazzi.RoborazziExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure + +class ScreenshotConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply("io.github.takahirom.roborazzi") + + extensions.configure { + outputDir.set(file("src/test/screenshots")) + } + } + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 69274059..59d7e687 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,7 @@ plugins { alias(libs.plugins.kover) alias(libs.plugins.ktlint) apply false alias(libs.plugins.licensee) apply false + alias(libs.plugins.roborazzi) apply false } tasks.register("clean").configure { diff --git a/gradle.properties b/gradle.properties index 226d2d56..28cdd439 100644 --- a/gradle.properties +++ b/gradle.properties @@ -37,3 +37,5 @@ POM_DEVELOPER_NAME=amazonwebservices POM_DEVELOPER_ORGANIZATION=Amazon Web Services POM_DEVELOPER_ORGANIZATION_URL=http://aws.amazon.com +# Verify roborazzy screenshots when running tests +roborazzi.test.verify=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7fc7a7f..77948700 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,8 +21,8 @@ ktlint = "11.0.0" licensee = "1.7.0" lifecycle = "2.4.0" mockk = "1.13.4" -paparazzi = "1.3.5" -robolectric = "4.9.2" +robolectric = "4.14.1" +roborazzi = "1.43.1" serialization = "1.3.3" tensorflow = "2.0.0" tensorflow-support = "0.3.0" @@ -80,6 +80,8 @@ test-junit = { module = "junit:junit", version.ref = "junit" } test-kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } test-robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } +test-roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref = "roborazzi" } +test-roborazzi-compose = { module = "io.github.takahirom.roborazzi:roborazzi-compose", version.ref = "roborazzi" } test-turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } # Dependencies for convention plugins @@ -89,6 +91,7 @@ plugin-kotlin-android = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", plugin-kover = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" } plugin-ktlint = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlint" } plugin-licensee = { module = "app.cash.licensee:app.cash.licensee.gradle.plugin", version.ref = "licensee" } +plugin-roborazzi = { module = "io.github.takahirom.roborazzi:io.github.takahirom.roborazzi.gradle.plugin", version.ref = "roborazzi" } [bundles] camera = ["androidx-camera-core", "androidx-camera-camera2", "androidx-camera-lifecycle"] @@ -98,6 +101,8 @@ test = [ "test-junit", "test-mockk", "test-robolectric", + "test-roborazzi", + "test-roborazzi-compose", "test-compose-junit", "test-compose-manifest", "test-coroutines", @@ -114,4 +119,4 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } licensee = { id = "app.cash.licensee", version.ref = "licensee" } -paparazzi = { id = "app.cash.paparazzi", version.ref = "paparazzi" } +roborazzi = { id = "io.github.takahirom.roborazzi", version.ref="roborazzi" } diff --git a/settings.gradle.kts b/settings.gradle.kts index dd5b24a8..35916131 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,6 @@ rootProject.name = "amplify-ui-android" include(":liveness") include(":authenticator") include(":testing") -include(":authenticator-screenshots") // Enable typesafe accessor generation for cross-project references enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/testing/build.gradle.kts b/testing/build.gradle.kts index f7f16d80..606ea8a8 100644 --- a/testing/build.gradle.kts +++ b/testing/build.gradle.kts @@ -26,4 +26,6 @@ dependencies { api(libs.bundles.test) implementation(libs.bundles.compose) + + implementation(libs.test.roborazzi) } diff --git a/testing/src/main/java/com/amplifyframework/ui/testing/ComposeTest.kt b/testing/src/main/java/com/amplifyframework/ui/testing/ComposeTest.kt index 025abb29..2be74310 100644 --- a/testing/src/main/java/com/amplifyframework/ui/testing/ComposeTest.kt +++ b/testing/src/main/java/com/amplifyframework/ui/testing/ComposeTest.kt @@ -22,24 +22,32 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers import org.junit.Before import org.junit.Rule import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode import org.robolectric.shadows.ShadowLog -@RunWith(RobolectricTestRunner::class) +@RunWith(AndroidJUnit4::class) +@GraphicsMode(GraphicsMode.Mode.NATIVE) +@Config(sdk = [33], qualifiers = RobolectricDeviceQualifiers.Pixel6) abstract class ComposeTest { @get:Rule val composeTestRule = createComposeRule() + @get:Rule + val screenshotRule = ScreenshotRule(composeTestRule) + @Before @Throws(Exception::class) fun setupLogging() { ShadowLog.stream = System.out // Redirect Logcat to console } - protected fun setContent(content: @Composable () -> Unit) = composeTestRule.setContent { + protected open fun setContent(content: @Composable () -> Unit) = composeTestRule.setContent { Box(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { content() } diff --git a/testing/src/main/java/com/amplifyframework/ui/testing/ScreenshotRule.kt b/testing/src/main/java/com/amplifyframework/ui/testing/ScreenshotRule.kt new file mode 100644 index 00000000..3a620538 --- /dev/null +++ b/testing/src/main/java/com/amplifyframework/ui/testing/ScreenshotRule.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amplifyframework.ui.testing + +import androidx.compose.ui.test.isRoot +import androidx.compose.ui.test.junit4.ComposeTestRule +import com.github.takahirom.roborazzi.RoborazziOptions +import com.github.takahirom.roborazzi.captureRoboImage +import java.io.File +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class ScreenshotTest + +/** + * Rule that captures a RoboRazzi screenshot only for annotated tests. To use this rule: + * + * 1. Extend the `ComposeTest` base class. + * 2. Write UI tests. + * 3. Annotate tests with `@ScreenshotTest` to have a screenshot automatically taken at the end of the test. + * + * Screenshot will be named "ClassName_function-name.png" + */ +class ScreenshotRule(val composeTestRule: ComposeTestRule) : TestRule { + + private val options = RoborazziOptions( + compareOptions = RoborazziOptions.CompareOptions( + // Allow a 0.5% difference when comparing to allow for platform rendering differences + changeThreshold = 0.005f + ) + ) + + override fun apply(base: Statement, description: Description): Statement = object : Statement() { + override fun evaluate() { + base.evaluate() + if (description.getAnnotation(ScreenshotTest::class.java) != null) { + composeTestRule.onNode(isRoot()).captureRoboImage( + file = File("src/test/screenshots", generateScreenshotName(description)), + roborazziOptions = options + ) + } + } + } + + private fun generateScreenshotName(description: Description): String { + val className = description.className.takeLastWhile { it != '.' } + val methodName = description.methodName.replace("\\s+".toRegex(), "-") + return "${className}_$methodName.png" + } +}