Skip to content

Commit

Permalink
Merge pull request #1129 from kul3r4/screenshottesting
Browse files Browse the repository at this point in the history
Adds screenshot tests to OAuth PKCE
  • Loading branch information
kul3r4 authored Jun 14, 2024
2 parents 547ea6c + e64b09e commit 02010fc
Show file tree
Hide file tree
Showing 25 changed files with 170 additions and 67 deletions.
4 changes: 1 addition & 3 deletions WearOAuth/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext{
compose_version = "1.5.14"
}
dependencies {
classpath libs.kotlin.gradle.plugin
}
Expand All @@ -27,6 +24,7 @@ buildscript {
plugins {
alias(libs.plugins.com.diffplug.spotless) apply(false)
alias(libs.plugins.com.android.application) apply(false)
alias(libs.plugins.roborazzi) apply false
}

subprojects {
Expand Down
2 changes: 1 addition & 1 deletion WearOAuth/oauth-device-grant/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerExtensionVersion libs.versions.compose.compiler.get()
}
testOptions {
unitTests {
Expand Down
42 changes: 35 additions & 7 deletions WearOAuth/oauth-pkce/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
alias libs.plugins.roborazzi
}

android {
Expand Down Expand Up @@ -50,34 +51,61 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerExtensionVersion libs.versions.compose.compiler.get()
}

testOptions {
unitTests {
includeAndroidResources true
}
}
}

dependencies {
def composeBom = platform(libs.androidx.compose.bom)

// General compose dependencies
implementation composeBom
implementation(libs.wear.compose.foundation)

// For Wear Material Design UX guidelines and specifications
implementation(libs.wear.compose.material)
implementation(libs.androidx.compose.material3)

// For integration between Wear Compose and Androidx Navigation libraries
implementation(libs.wear.compose.navigation)

// For Wear preview annotations
implementation libs.compose.ui.tooling.preview
implementation(libs.androidx.compose.ui.tooling)

// Horologist dependencies
implementation(libs.horologist.compose.layout)

// Standard android deps
// Standard android dependencies
implementation projects.util
implementation libs.kotlin.stdlib
implementation libs.androidx.core.ktx
implementation libs.androidx.appcompat
implementation(libs.kotlin.stdlib)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.wear)
implementation(libs.wear.phone.interactions)
implementation libs.playservices.wearable
implementation(libs.playservices.wearable)

implementation libs.androidx.ui.test.manifest

// Testing
testImplementation libs.androidx.ui.test.junit4
testImplementation libs.junit
testImplementation libs.robolectric
testImplementation libs.roborazzi
testImplementation libs.roborazzi.compose
testImplementation libs.roborazzi.rule
testImplementation(libs.horologist.roboscreenshots) {
exclude(group: "com.github.QuickBirdEng.kotlin-snapshot-testing")
}

debugImplementation libs.compose.ui.tooling
debugImplementation libs.androidx.ui.test.manifest
debugImplementation composeBom
}
3 changes: 2 additions & 1 deletion WearOAuth/oauth-pkce/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<application
android:allowBackup="false"
android:label="OAuth PKCE"
android:icon="@mipmap/ic_launcher"
android:theme="@android:style/Theme.DeviceDefault">

<meta-data
Expand All @@ -35,7 +36,7 @@

<activity
android:name=".AuthPKCEActivity"
android:label="wearoauth"
android:label="wearoauth PKCE"
android:taskAffinity=".main"
android:exported="true"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ package com.example.android.wearable.oauth.pkce
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ListHeader
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.ListHeader
import androidx.wear.compose.material.Text
import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices
import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.AppScaffold
import com.google.android.horologist.compose.layout.ScalingLazyColumn
Expand All @@ -55,42 +55,76 @@ class AuthPKCEActivity : ComponentActivity() {
}
}

@OptIn(ExperimentalHorologistApi::class)
@Composable
fun PKCEApp(pkceViewModel: AuthPKCEViewModel) {
AppScaffold {
val uiState = pkceViewModel.uiState.collectAsState()
val localContext = LocalContext.current
val columnState = rememberResponsiveColumnState(
contentPadding = ScalingLazyColumnDefaults.padding(
first = ScalingLazyColumnDefaults.ItemType.Text,
last = ScalingLazyColumnDefaults.ItemType.Text
)
AuthenticateScreen(
uiState.value.statusCode,
uiState.value.resultMessage,
pkceViewModel::startAuthFlow
)
ScreenScaffold(scrollState = columnState) {
ScalingLazyColumn(columnState = columnState) {
item {
ListHeader {
Text(
stringResource(R.string.oauth_pkce),
textAlign = TextAlign.Center
)
}
}
}

@OptIn(ExperimentalHorologistApi::class)
@Composable
fun AuthenticateScreen(
statusCode: Int,
resultMessage: String,
startAuthFlow: () -> Unit
) {
val columnState = rememberResponsiveColumnState(
contentPadding = ScalingLazyColumnDefaults.padding(
first = ScalingLazyColumnDefaults.ItemType.Text,
last = ScalingLazyColumnDefaults.ItemType.Text
)
)
ScreenScaffold(scrollState = columnState) {
ScalingLazyColumn(columnState = columnState) {
item {
ListHeader {
Text(
stringResource(R.string.oauth_pkce),
textAlign = TextAlign.Center
)
}
item {
Button(
onClick = { pkceViewModel.startAuthFlow(localContext) },
modifier = Modifier.fillMaxSize()
) {
}
item {
Chip(
onClick = { startAuthFlow() },
label = {
Text(
text = stringResource(R.string.authenticate),
modifier = Modifier.align(Alignment.CenterVertically)
)
}
}
item { Text(stringResource(id = uiState.value.statusCode)) }
item { Text(uiState.value.resultMessage) }
)
}
item { Text(stringResource(id = statusCode)) }
item { Text(resultMessage) }
}
}
}

@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun AuthenticateScreenPreview() {
AuthenticateScreen(
statusCode = R.string.status_retrieved,
resultMessage = "Bobby Bonson",
startAuthFlow = {}
)
}

@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun AuthenticateScreenFailedPreview() {
AuthenticateScreen(
statusCode = R.string.status_failed,
resultMessage = "",
startAuthFlow = {}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
*/
package com.example.android.wearable.oauth.pkce

import android.app.Application
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import androidx.wear.phone.interactions.authentication.CodeChallenge
import androidx.wear.phone.interactions.authentication.CodeVerifier
Expand Down Expand Up @@ -55,7 +56,8 @@ data class ProofKeyCodeExchangeState(
* different steps of the flow. It first retrieves the OAuth code, uses it to exchange it for an
* access token, and uses the token to retrieve the user's name.
*/
class AuthPKCEViewModel : ViewModel() {
class AuthPKCEViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
private val _uiState = MutableStateFlow(ProofKeyCodeExchangeState())
val uiState: StateFlow<ProofKeyCodeExchangeState> = _uiState.asStateFlow()

Expand All @@ -74,7 +76,7 @@ class AuthPKCEViewModel : ViewModel() {
* the phone. After the user consents on their phone, the wearable app is notified and can
* continue the authorization process.
*/
fun startAuthFlow(context: Context) {
fun startAuthFlow() {
viewModelScope.launch {
val codeVerifier = CodeVerifier()

Expand Down
6 changes: 3 additions & 3 deletions WearOAuth/oauth-pkce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
<resources>
<string name="start_auth_flow">Authenticate</string>
<string name="oauth_pkce">OAuth PKCE</string>
<string name="status_switch_to_phone">Starting authorization… Switch to your phone to authenticate.</string>
<string name="status_failed">Authorization failed</string>
<string name="status_switch_to_phone">Starting authentication… Switch to your phone to authenticate.</string>
<string name="status_failed">Authentication failed</string>
<string name="status_retrieving_token">Retrieving token…</string>
<string name="status_failure_token">Could not retrieve token</string>
<string name="status_retrieving_user">Retrieving user profile…</string>
<string name="status_failure_user">Could not retrieve user profile</string>
<string name="status_retrieved">User profile retrieved. Name:</string>
<string name="status_retrieved">User profile retrieved.\nName:</string>
<string name="authenticate">Authenticate</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.example.android.wearable.oauth.pkce

import com.google.android.horologist.compose.layout.AppScaffold
import com.google.android.horologist.compose.layout.ResponsiveTimeText
import com.google.android.horologist.screenshots.FixedTimeSource
import com.google.android.horologist.screenshots.rng.WearDevice
import com.google.android.horologist.screenshots.rng.WearScreenshotTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner

@RunWith(ParameterizedRobolectricTestRunner::class)
class AuthenticateScreenTest(override val device: WearDevice) : WearScreenshotTest() {
override val tolerance = 0.02f

// code to make sure multiple tests are run
override fun testName(suffix: String): String =
"src/test/snapshots/images/" +
"${this.javaClass.`package`?.name}_${this.javaClass.simpleName}_" +
"${testInfo.methodName}$suffix.png"

@Test
fun authenticateScreenTest() = runTest {
AppScaffold(
timeText = { ResponsiveTimeText(timeSource = FixedTimeSource) }
) {
AuthenticateScreenPreview()
}
}

@Test
fun authenticateFailedScreenTest() = runTest {
AppScaffold(
timeText = { ResponsiveTimeText(timeSource = FixedTimeSource) }
) {
AuthenticateScreenFailedPreview()
}
}

companion object {
@JvmStatic
@ParameterizedRobolectricTestRunner.Parameters
fun devices() = WearDevice.entries
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 0 additions & 20 deletions WearOAuth/util/src/main/AndroidManifest.xml

This file was deleted.

0 comments on commit 02010fc

Please sign in to comment.