diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 00ada2a1c..d77a706b3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,6 +18,7 @@ updates: - "org.jetbrains.kotlin.jvm" - "com.google.devtools.ksp" - "androidx.compose.compiler:compiler" + open-pull-requests-limit: 10 registries: maven-google: type: "maven-repository" diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index d47342c6a..2e6d11841 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -31,7 +31,7 @@ jobs: run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 @@ -75,7 +75,7 @@ jobs: # Run local tests after screenshot tests to avoid wrong UP-TO-DATE. TODO: Ignore screenshots. - name: Run local tests if: always() - run: ./gradlew testDemoDebug testProdDebug :lint:test + run: ./gradlew testDemoDebug :lint:test # Replace task exclusions with `-Pandroidx.baselineprofile.skipgeneration` when # https://android-review.googlesource.com/c/platform/frameworks/support/+/2602790 landed in a # release build @@ -91,14 +91,14 @@ jobs: -x collectProdNonMinifiedBenchmarkBaselineProfile - name: Upload build outputs (APKs) - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: APKs path: '**/build/outputs/apk/**/*.apk' - name: Upload test results (XML) if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-results path: '**/build/test-results/test*UnitTest/**.xml' @@ -108,7 +108,7 @@ jobs: - name: Upload lint reports (HTML) if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: lint-reports path: '**/build/reports/lint-results-*.html' @@ -131,7 +131,7 @@ jobs: run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 @@ -154,7 +154,7 @@ jobs: - name: Upload test reports if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-reports-${{ matrix.api-level }} path: '**/build/reports/androidTests' diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index f85215b74..f4901b9e2 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -21,7 +21,7 @@ jobs: run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 diff --git a/README.md b/README.md index b71427dfe..6f13f5de2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The app is currently in development. The `prodRelease` variant is [available on **Now in Android** displays content from the [Now in Android](https://developer.android.com/series/now-in-android) series. Users can browse for links to recent videos, articles and other content. Users can also follow topics they are interested -in. +in, and be notified when new content is published which matches interests they are following. ## Screenshots @@ -109,12 +109,42 @@ Examples: manipulate the state of the `Test` repository and verify the resulting behavior, instead of checking that specific repository methods were called. -## Screenshot tests +To run the tests execute the following gradle tasks: + +- `testDemoDebug` run all local tests against the `demoDebug` variant. +- `connectedDemoDebugAndroidTest` run all instrumented tests against the `demoDebug` variant. -**Now In Android** uses [Roborazzi](https://github.com/takahirom/roborazzi) to do screenshot tests -of certain screens and components. To run these tests, run the `verifyRoborazziDemoDebug` or -`recordRoborazziDemoDebug` tasks. Note that screenshots are recorded on CI, using Linux, and other -platforms might generate slightly different images, making the tests fail. +**Note:** You should not run `./gradlew test` or `./gradlew connectedAndroidTest` as this will execute +tests against _all_ build variants which is both unecessary and will result in failures as only the +`demoDebug` variant is supported. No other variants have any tests (although this might change in future). + +## Screenshot tests +A screenshot test takes a screenshot of a screen or a UI component within the app, and compares it +with a previously recorded screenshot which is known to be rendered correctly. + +For example, Now in Android has [screenshot tests](https://github.com/android/nowinandroid/blob/main/app/src/testDemoDebug/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt) +to verify that the navigation is displayed correctly on different screen sizes +([known correct screenshots](https://github.com/android/nowinandroid/tree/main/app/src/testDemoDebug/screenshots)). + +Now In Android uses [Roborazzi](https://github.com/takahirom/roborazzi) to run screenshot tests +of certain screens and UI components. When working with screenshot tests the following gradle tasks are useful: + +- `verifyRoborazziDemoDebug` run all screenshot tests, verifying the screenshots against the known +correct screenshots. +- `recordRoborazziDemoDebug` record new "known correct" screenshots. Use this command when you have +made changes to the UI and manually verified that they are rendered correctly. Screenshots will be +stored in `modulename/src/test/screenshots`. +- `compareRoborazziDemoDebug` create comparison images between failed tests and the known correct +images. These can also be found in `modulename/src/test/screenshots`. + +**Note:** The known correct screenshots stored in this repository are recorded on CI using Linux. Other +platforms may (and probably will) generate slightly different images, making the screenshot tests fail. +When working on a non-Linux platform, a workaround to this is to run `recordRoborazziDemoDebug` on the +`main` branch before starting work. After making changes, `verifyRoborazziDemoDebug` will identify only +legitimate changes. + +For more information about screenshot testing +[check out this talk](https://www.droidcon.com/2023/11/15/easy-screenshot-testing-with-compose/). # UI The app was designed using [Material 3 guidelines](https://m3.material.io/). Learn more about the design process and diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index ea57a7c8c..e11a1b42a 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { compileOnly(libs.firebase.performance.gradlePlugin) compileOnly(libs.kotlin.gradlePlugin) compileOnly(libs.ksp.gradlePlugin) + implementation(libs.truth) } tasks { diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt index 2a776e429..c59d3ffb8 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt @@ -20,8 +20,8 @@ import com.android.build.api.artifact.SingleArtifact import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.gradle.BaseExtension import com.android.SdkConstants +import com.google.common.truth.Truth.assertWithMessage import org.gradle.api.DefaultTask -import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty @@ -40,7 +40,6 @@ import org.gradle.kotlin.dsl.register import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.process.ExecOperations import java.io.File -import java.nio.file.Files import javax.inject.Inject @CacheableTask @@ -98,17 +97,12 @@ abstract class CheckBadgingTask : DefaultTask() { @TaskAction fun taskAction() { - if ( - Files.mismatch( - goldenBadging.get().asFile.toPath(), - generatedBadging.get().asFile.toPath(), - ) != -1L - ) { - throw GradleException( - "Generated badging is different from golden badging! " + - "If this change is intended, run ./gradlew ${updateBadgingTaskName.get()}", - ) - } + assertWithMessage( + "Generated badging is different from golden badging! " + + "If this change is intended, run ./gradlew ${updateBadgingTaskName.get()}", + ) + .that(generatedBadging.get().asFile.readText()) + .isEqualTo(goldenBadging.get().asFile.readText()) } } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt index 04ee4e56e..903c84d8f 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt @@ -83,10 +83,8 @@ private fun Project.configureKotlin() { val warningsAsErrors: String? by project allWarningsAsErrors = warningsAsErrors.toBoolean() freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=kotlin.RequiresOptIn", // Enable experimental coroutines APIs, including Flow "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=kotlinx.coroutines.FlowPreview", ) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 67b267cdc..c644ae3d5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,21 +1,22 @@ [versions] accompanist = "0.32.0" -androidDesugarJdkLibs = "2.0.3" +androidDesugarJdkLibs = "2.0.4" # AGP and tools should be updated together -androidGradlePlugin = "8.1.3" +androidGradlePlugin = "8.2.0" androidTools = "31.1.3" androidxActivity = "1.8.0" androidxAppCompat = "1.6.1" androidxBrowser = "1.6.0" androidxComposeBom = "2023.10.01" -androidxComposeCompiler = "1.5.3" +androidxComposeCompiler = "1.5.7" +androidxComposeRuntimeTracing = "1.0.0-beta01" androidxCore = "1.12.0" androidxCoreSplashscreen = "1.0.1" androidxDataStore = "1.0.0" androidxEspresso = "3.5.1" androidxHiltNavigationCompose = "1.0.0" androidxLifecycle = "2.6.2" -androidxMacroBenchmark = "1.2.0" +androidxMacroBenchmark = "1.2.2" androidxMetrics = "1.0.0-alpha04" androidxNavigation = "2.7.4" androidxProfileinstaller = "1.3.1" @@ -25,8 +26,9 @@ androidxTestRules = "1.5.0" androidxTestRunner = "1.5.2" androidxTracing = "1.1.0" androidxUiAutomator = "2.2.0" +androidxWindowManager = "1.2.0" androidxWork = "2.9.0-rc01" -coil = "2.4.0" +coil = "2.5.0" dependencyGuard = "0.4.3" firebaseBom = "32.4.0" firebaseCrashlyticsPlugin = "2.9.9" @@ -34,15 +36,16 @@ firebasePerfPlugin = "1.4.2" gmsPlugin = "4.4.0" googleOss = "17.0.1" googleOssPlugin = "0.10.6" -hilt = "2.48.1" +hilt = "2.50" hiltExt = "1.1.0" jacoco = "0.8.7" -kotlin = "1.9.10" +junit4 = "4.13.2" +kotlin = "1.9.21" kotlinxCoroutines = "1.7.3" -kotlinxDatetime = "0.4.1" +kotlinxDatetime = "0.5.0" kotlinxSerializationJson = "1.6.0" -ksp = "1.9.10-1.0.13" -lint = "31.1.3" +ksp = "1.9.21-1.0.16" +lint = "31.2.0" okhttp = "4.12.0" protobuf = "3.24.4" protobufPlugin = "0.9.4" @@ -50,8 +53,9 @@ retrofit = "2.9.0" retrofitKotlinxSerializationJson = "1.0.0" robolectric = "4.11.1" roborazzi = "1.6.0" -room = "2.6.0" +room = "2.6.1" secrets = "2.0.1" +truth = "1.1.5" turbine = "1.0.0" [libraries] @@ -128,6 +132,7 @@ roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", versi room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +truth = { group = "com.google.truth", name = "truth", version.ref = "truth" } turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } # Dependencies of the included build-logic