Merge branch 'main' of github.com:android/nowinandroid into rpf-update-firebase-ktx

# Conflicts:
#	gradle/libs.versions.toml
pull/1006/head
rosariopf 1 year ago
commit 5a42e1fe19

@ -47,7 +47,7 @@ jobs:
path: '**/build/outputs/apk/**/*.apk'
- name: Run local tests
run: ./gradlew testDemoDebug testProdDebug
run: ./gradlew testDemoDebug testProdDebug :lint:test
test:
runs-on: ubuntu-latest
@ -125,6 +125,9 @@ jobs:
name: lint-reports
path: '**/build/reports/lint-results-*.html'
- name: Check badging
run: ./gradlew :app:checkProdReleaseBadging
androidTest:
needs: build
runs-on: macOS-latest # enables hardware acceleration in the virtual machine

@ -0,0 +1,121 @@
package: name='com.google.samples.apps.nowinandroid' versionCode='8' versionName='0.1.2' platformBuildVersionName='14' platformBuildVersionCode='34' compileSdkVersion='34' compileSdkVersionCodename='14'
sdkVersion:'21'
targetSdkVersion:'34'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.POST_NOTIFICATIONS'
uses-permission: name='android.permission.WAKE_LOCK'
uses-permission: name='com.google.android.c2dm.permission.RECEIVE'
uses-permission: name='com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE'
uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED'
uses-permission: name='android.permission.FOREGROUND_SERVICE'
uses-permission: name='com.google.samples.apps.nowinandroid.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION'
application-label:'Now in Android'
application-label-af:'Now in Android'
application-label-am:'Now in Android'
application-label-ar:'Now in Android'
application-label-as:'Now in Android'
application-label-az:'Now in Android'
application-label-be:'Now in Android'
application-label-bg:'Now in Android'
application-label-bn:'Now in Android'
application-label-bs:'Now in Android'
application-label-ca:'Now in Android'
application-label-cs:'Now in Android'
application-label-da:'Now in Android'
application-label-de:'Now in Android'
application-label-el:'Now in Android'
application-label-en-AU:'Now in Android'
application-label-en-CA:'Now in Android'
application-label-en-GB:'Now in Android'
application-label-en-IN:'Now in Android'
application-label-en-XC:'Now in Android'
application-label-es:'Now in Android'
application-label-es-US:'Now in Android'
application-label-et:'Now in Android'
application-label-eu:'Now in Android'
application-label-fa:'Now in Android'
application-label-fi:'Now in Android'
application-label-fr:'Now in Android'
application-label-fr-CA:'Now in Android'
application-label-gl:'Now in Android'
application-label-gu:'Now in Android'
application-label-hi:'Now in Android'
application-label-hr:'Now in Android'
application-label-hu:'Now in Android'
application-label-hy:'Now in Android'
application-label-in:'Now in Android'
application-label-is:'Now in Android'
application-label-it:'Now in Android'
application-label-iw:'Now in Android'
application-label-ja:'Now in Android'
application-label-ka:'Now in Android'
application-label-kk:'Now in Android'
application-label-km:'Now in Android'
application-label-kn:'Now in Android'
application-label-ko:'Now in Android'
application-label-ky:'Now in Android'
application-label-lo:'Now in Android'
application-label-lt:'Now in Android'
application-label-lv:'Now in Android'
application-label-mk:'Now in Android'
application-label-ml:'Now in Android'
application-label-mn:'Now in Android'
application-label-mr:'Now in Android'
application-label-ms:'Now in Android'
application-label-my:'Now in Android'
application-label-nb:'Now in Android'
application-label-ne:'Now in Android'
application-label-nl:'Now in Android'
application-label-or:'Now in Android'
application-label-pa:'Now in Android'
application-label-pl:'Now in Android'
application-label-pt:'Now in Android'
application-label-pt-BR:'Now in Android'
application-label-pt-PT:'Now in Android'
application-label-ro:'Now in Android'
application-label-ru:'Now in Android'
application-label-si:'Now in Android'
application-label-sk:'Now in Android'
application-label-sl:'Now in Android'
application-label-sq:'Now in Android'
application-label-sr:'Now in Android'
application-label-sr-Latn:'Now in Android'
application-label-sv:'Now in Android'
application-label-sw:'Now in Android'
application-label-ta:'Now in Android'
application-label-te:'Now in Android'
application-label-th:'Now in Android'
application-label-tl:'Now in Android'
application-label-tr:'Now in Android'
application-label-uk:'Now in Android'
application-label-ur:'Now in Android'
application-label-uz:'Now in Android'
application-label-vi:'Now in Android'
application-label-zh-CN:'Now in Android'
application-label-zh-HK:'Now in Android'
application-label-zh-TW:'Now in Android'
application-label-zu:'Now in Android'
application-icon-120:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-160:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-240:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-320:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-480:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-640:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-65534:'res/mipmap-anydpi-v26/ic_launcher.xml'
application: label='Now in Android' icon='res/mipmap-anydpi-v26/ic_launcher.xml'
launchable-activity: name='com.google.samples.apps.nowinandroid.MainActivity' label='' icon=''
uses-library-not-required:'androidx.window.extensions'
uses-library-not-required:'androidx.window.sidecar'
uses-library-not-required:'android.ext.adservices'
feature-group: label=''
uses-feature: name='android.hardware.faketouch'
uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
main
other-activities
other-receivers
other-services
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--' 'af' 'am' 'ar' 'as' 'az' 'be' 'bg' 'bn' 'bs' 'ca' 'cs' 'da' 'de' 'el' 'en-AU' 'en-CA' 'en-GB' 'en-IN' 'en-XC' 'es' 'es-US' 'et' 'eu' 'fa' 'fi' 'fr' 'fr-CA' 'gl' 'gu' 'hi' 'hr' 'hu' 'hy' 'in' 'is' 'it' 'iw' 'ja' 'ka' 'kk' 'km' 'kn' 'ko' 'ky' 'lo' 'lt' 'lv' 'mk' 'ml' 'mn' 'mr' 'ms' 'my' 'nb' 'ne' 'nl' 'or' 'pa' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'si' 'sk' 'sl' 'sq' 'sr' 'sr-Latn' 'sv' 'sw' 'ta' 'te' 'th' 'tl' 'tr' 'uk' 'ur' 'uz' 'vi' 'zh-CN' 'zh-HK' 'zh-TW' 'zu'
densities: '120' '160' '240' '320' '480' '640' '65534'

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 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
http://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.
-->
<resources>
<color name="ic_launcher_background_tint">#FFFFFF</color>
<color name="ic_launcher_foreground_tint">#FF006780</color>
</resources>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 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
http://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.
-->
<resources>
<color name="ic_launcher_background_tint">#000000</color>
<color name="ic_launcher_foreground_tint">#FF006780</color>
</resources>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 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
http://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.
-->
<resources>
<color name="ic_launcher_background_tint">#FFFFFF</color>
<color name="ic_launcher_foreground_tint">#FFA23F16</color>
</resources>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 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
http://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.
-->
<resources>
<color name="ic_launcher_background_tint">#000000</color>
<color name="ic_launcher_foreground_tint">#FFA23F16</color>
</resources>

@ -20,6 +20,7 @@ import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.google.samples.apps.nowinandroid.flingElementDownUp
import com.google.samples.apps.nowinandroid.waitAndFindObject
fun MacrobenchmarkScope.goToInterestsScreen() {
device.findObject(By.text("Interests")).click()
@ -34,7 +35,7 @@ fun MacrobenchmarkScope.goToInterestsScreen() {
}
fun MacrobenchmarkScope.interestsScrollTopicsDownUp() {
val topicsList = device.wait(Until.findObject(By.res("interests:topics")), 2_000)
val topicsList = device.waitAndFindObject(By.res("interests:topics"), 2_000)
device.flingElementDownUp(topicsList)
}

@ -17,11 +17,14 @@
import com.android.build.api.dsl.ApplicationExtension
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import com.google.samples.apps.nowinandroid.configureBadgingTasks
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
import com.google.samples.apps.nowinandroid.configurePrintApksTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.getByType
class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
@ -39,6 +42,7 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
}
extensions.configure<ApplicationAndroidComponentsExtension> {
configurePrintApksTask(this)
configureBadgingTasks(extensions.getByType<BaseExtension>(), this)
}
}
}

@ -0,0 +1,143 @@
/*
* Copyright 2023 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.google.samples.apps.nowinandroid
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.BaseExtension
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
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.configure
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
abstract class GenerateBadgingTask : DefaultTask() {
@get:OutputFile
abstract val badging: RegularFileProperty
@get:InputFile
abstract val apk: RegularFileProperty
@get:InputFile
abstract val aapt2Executable: RegularFileProperty
@get:Inject
abstract val execOperations: ExecOperations
@TaskAction
fun taskAction() {
execOperations.exec {
commandLine(
aapt2Executable.get().asFile.absolutePath,
"dump",
"badging",
apk.get().asFile.absolutePath
)
standardOutput = badging.asFile.get().outputStream()
}
}
}
abstract class CheckBadgingTask : DefaultTask() {
// In order for the task to be up-to-date when the inputs have not changed,
// the task must declare an output, even if it's not used. Tasks with no
// output are always run regardless of whether the inputs changed
@get:OutputDirectory
abstract val output: DirectoryProperty
@get:InputFile
abstract val goldenBadging: RegularFileProperty
@get:InputFile
abstract val generatedBadging: RegularFileProperty
override fun getGroup(): String = LifecycleBasePlugin.VERIFICATION_GROUP
@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 updateBadging"
)
}
}
}
fun Project.configureBadgingTasks(
baseExtension: BaseExtension,
componentsExtension: ApplicationAndroidComponentsExtension
) {
// Registers a callback to be called, when a new variant is configured
componentsExtension.onVariants { variant ->
// Registers a new task to verify the app bundle.
val generateBadging = tasks.register<GenerateBadgingTask>("generate${variant.name}Badging") {
apk.set(
variant.artifacts.get(SingleArtifact.APK_FROM_BUNDLE)
)
aapt2Executable.set(
File(
baseExtension.sdkDirectory,
"build-tools/${baseExtension.buildToolsVersion}/aapt2"
)
)
badging.set(
project.layout.buildDirectory.file(
"outputs/apk_from_bundle/${variant.name}/${variant.name}-badging.txt"
)
)
}
tasks.register<Copy>("update${variant.name}Badging") {
from(generateBadging.get().badging)
into(project.layout.projectDirectory)
}
val checkBadgingTaskName = "check${variant.name}Badging"
tasks.register<CheckBadgingTask>(checkBadgingTaskName) {
goldenBadging.set(
project.layout.projectDirectory.file("${variant.name}-badging.txt"))
generatedBadging.set(
generateBadging.get().badging
)
output.set(
project.layout.buildDirectory.dir("intermediates/$checkBadgingTaskName")
)
}
}
}

@ -87,6 +87,6 @@ data class UserEditableSettings(
)
sealed interface SettingsUiState {
object Loading : SettingsUiState
data object Loading : SettingsUiState
data class Success(val settings: UserEditableSettings) : SettingsUiState
}

@ -177,12 +177,12 @@ private fun newsUiState(
sealed interface TopicUiState {
data class Success(val followableTopic: FollowableTopic) : TopicUiState
object Error : TopicUiState
object Loading : TopicUiState
data object Error : TopicUiState
data object Loading : TopicUiState
}
sealed interface NewsUiState {
data class Success(val news: List<UserNewsResource>) : NewsUiState
object Error : NewsUiState
object Loading : NewsUiState
data object Error : NewsUiState
data object Loading : NewsUiState
}

@ -5,7 +5,7 @@ androidGradlePlugin = "8.1.2"
androidxActivity = "1.8.0"
androidxAppCompat = "1.6.1"
androidxBrowser = "1.6.0"
androidxComposeBom = "2023.10.00"
androidxComposeBom = "2023.10.01"
androidxComposeCompiler = "1.5.3"
androidxComposeRuntimeTracing = "1.0.0-alpha03"
androidxCore = "1.12.0"
@ -14,7 +14,7 @@ androidxDataStore = "1.0.0"
androidxEspresso = "3.5.1"
androidxHiltNavigationCompose = "1.0.0"
androidxLifecycle = "2.6.2"
androidxMacroBenchmark = "1.2.0-rc02"
androidxMacroBenchmark = "1.2.0"
androidxMetrics = "1.0.0-alpha04"
androidxNavigation = "2.7.4"
androidxProfileinstaller = "1.3.1"
@ -26,7 +26,7 @@ androidxTestRunner = "1.5.2"
androidxTracing = "1.1.0"
androidxUiAutomator = "2.2.0"
androidxWindowManager = "1.1.0"
androidxWork = "2.9.0-beta01"
androidxWork = "2.9.0-rc01"
coil = "2.4.0"
firebaseBom = "32.5.0"
firebaseCrashlyticsPlugin = "2.9.9"
@ -44,14 +44,14 @@ kotlinxDatetime = "0.4.1"
kotlinxSerializationJson = "1.6.0"
ksp = "1.9.10-1.0.13"
lint = "31.1.2"
okhttp = "4.11.0"
okhttp = "4.12.0"
protobuf = "3.24.4"
protobufPlugin = "0.9.4"
retrofit = "2.9.0"
retrofitKotlinxSerializationJson = "1.0.0"
robolectric = "4.10.3"
roborazzi = "1.5.0"
room = "2.5.2"
roborazzi = "1.6.0"
room = "2.6.0"
secrets = "2.0.1"
turbine = "1.0.0"
@ -122,6 +122,8 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
lint-api = { group = "com.android.tools.lint", name = "lint-api", version.ref = "lint" }
lint-checks = { group = "com.android.tools.lint", name = "lint-checks", version.ref = "lint" }
lint-tests = { group = "com.android.tools.lint", name = "lint-tests", version.ref = "lint" }
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" }
protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" }

@ -38,4 +38,7 @@ tasks.withType<KotlinCompile>().configureEach {
dependencies {
compileOnly(libs.kotlin.stdlib)
compileOnly(libs.lint.api)
testImplementation(libs.lint.checks)
testImplementation(libs.lint.tests)
testImplementation(kotlin("test"))
}

@ -14,18 +14,20 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.lint.designsystem
package com.google.samples.apps.nowinandroid.lint
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.google.samples.apps.nowinandroid.lint.designsystem.DesignSystemDetector
/**
* An issue registry that checks for incorrect usages of Compose Material APIs over equivalents in
* the Now in Android design system module.
*/
class DesignSystemIssueRegistry : IssueRegistry() {
override val issues = listOf(DesignSystemDetector.ISSUE)
class NiaIssueRegistry : IssueRegistry() {
override val issues = listOf(
DesignSystemDetector.ISSUE,
TestMethodNameDetector.FORMAT,
TestMethodNameDetector.PREFIX,
)
override val api: Int = CURRENT_API

@ -0,0 +1,126 @@
/*
* Copyright 2023 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.google.samples.apps.nowinandroid.lint
import com.android.tools.lint.detector.api.AnnotationInfo
import com.android.tools.lint.detector.api.AnnotationUsageInfo
import com.android.tools.lint.detector.api.Category.Companion.TESTING
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Scope.JAVA_FILE
import com.android.tools.lint.detector.api.Scope.TEST_SOURCES
import com.android.tools.lint.detector.api.Severity.WARNING
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.TextFormat.RAW
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UElement
import java.util.EnumSet
import kotlin.io.path.Path
/**
* A detector that checks for common patterns in naming the test methods:
* - [detectPrefix] removes unnecessary "test" prefix in all unit test.
* - [detectFormat] Checks the `given_when_then` format of Android instrumented tests (backticks are not supported).
*/
class TestMethodNameDetector : Detector(), SourceCodeScanner {
override fun applicableAnnotations() = listOf("org.junit.Test")
override fun visitAnnotationUsage(
context: JavaContext,
element: UElement,
annotationInfo: AnnotationInfo,
usageInfo: AnnotationUsageInfo,
) {
val method = usageInfo.referenced as? PsiMethod ?: return
method.detectPrefix(context, usageInfo)
method.detectFormat(context, usageInfo)
}
private fun JavaContext.isAndroidTest() = Path("androidTest") in file.toPath()
private fun PsiMethod.detectPrefix(
context: JavaContext,
usageInfo: AnnotationUsageInfo,
) {
if (!name.startsWith("test")) return
context.report(
issue = PREFIX,
scope = usageInfo.usage,
location = context.getNameLocation(this),
message = PREFIX.getBriefDescription(RAW),
quickfixData = LintFix.create()
.name("Remove prefix")
.replace().pattern("""test[\s_]*""")
.with("")
.autoFix()
.build(),
)
}
private fun PsiMethod.detectFormat(
context: JavaContext,
usageInfo: AnnotationUsageInfo,
) {
if (!context.isAndroidTest()) return
if ("""[^\W_]+(_[^\W_]+){1,2}""".toRegex().matches(name)) return
context.report(
issue = FORMAT,
scope = usageInfo.usage,
location = context.getNameLocation(this),
message = FORMAT.getBriefDescription(RAW),
)
}
companion object {
private fun issue(
id: String,
briefDescription: String,
explanation: String,
): Issue = Issue.create(
id = id,
briefDescription = briefDescription,
explanation = explanation,
category = TESTING,
priority = 5,
severity = WARNING,
implementation = Implementation(
TestMethodNameDetector::class.java,
EnumSet.of(JAVA_FILE, TEST_SOURCES),
),
)
@JvmField
val PREFIX: Issue = issue(
id = "TestMethodPrefix",
briefDescription = "Test method starts with `test`",
explanation = "Test method should not start with `test`.",
)
@JvmField
val FORMAT: Issue = issue(
id = "TestMethodFormat",
briefDescription = "Test method does not follow the `given_when_then` or `when_then` format",
explanation = "Test method should follow the `given_when_then` or `when_then` format.",
)
}
}

@ -14,4 +14,4 @@
# limitations under the License.
#
com.google.samples.apps.nowinandroid.lint.designsystem.DesignSystemIssueRegistry
com.google.samples.apps.nowinandroid.lint.NiaIssueRegistry

@ -0,0 +1,123 @@
/*
* Copyright 2023 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.google.samples.apps.nowinandroid.lint
import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
import com.google.samples.apps.nowinandroid.lint.TestMethodNameDetector.Companion.FORMAT
import com.google.samples.apps.nowinandroid.lint.TestMethodNameDetector.Companion.PREFIX
import org.junit.Test
class TestMethodNameDetectorTest {
@Test
fun `detect prefix`() {
lint().issues(PREFIX)
.files(
JUNIT_TEST_STUB,
kotlin(
"""
import org.junit.Test
class Test {
@Test
fun foo() = Unit
@Test
fun test_foo() = Unit
@Test
fun `test foo`() = Unit
}
""",
).indented(),
)
.run()
.expect(
"""
src/Test.kt:6: Warning: Test method starts with test [TestMethodPrefix]
fun test_foo() = Unit
~~~~~~~~
src/Test.kt:8: Warning: Test method starts with test [TestMethodPrefix]
fun `test foo`() = Unit
~~~~~~~~~~
0 errors, 2 warnings
""".trimIndent(),
)
.expectFixDiffs(
"""
Autofix for src/Test.kt line 6: Remove prefix:
@@ -6 +6
- fun test_foo() = Unit
+ fun foo() = Unit
Autofix for src/Test.kt line 8: Remove prefix:
@@ -8 +8
- fun `test foo`() = Unit
+ fun `foo`() = Unit
""".trimIndent(),
)
}
@Test
fun `detect format`() {
lint().issues(FORMAT)
.files(
JUNIT_TEST_STUB,
kotlin(
"src/androidTest/com/example/Test.kt",
"""
import org.junit.Test
class Test {
@Test
fun when_then() = Unit
@Test
fun given_when_then() = Unit
@Test
fun foo() = Unit
@Test
fun foo_bar_baz_qux() = Unit
@Test
fun `foo bar baz`() = Unit
}
""",
).indented(),
)
.run()
.expect(
"""
src/androidTest/com/example/Test.kt:9: Warning: Test method does not follow the given_when_then or when_then format [TestMethodFormat]
fun foo() = Unit
~~~
src/androidTest/com/example/Test.kt:11: Warning: Test method does not follow the given_when_then or when_then format [TestMethodFormat]
fun foo_bar_baz_qux() = Unit
~~~~~~~~~~~~~~~
src/androidTest/com/example/Test.kt:13: Warning: Test method does not follow the given_when_then or when_then format [TestMethodFormat]
fun `foo bar baz`() = Unit
~~~~~~~~~~~~~
0 errors, 3 warnings
""".trimIndent(),
)
}
private companion object {
private val JUNIT_TEST_STUB: TestFile = kotlin(
"""
package org.junit
annotation class Test
""",
).indented()
}
}
Loading…
Cancel
Save