Add tests for the introduced fixes

Add a test for applying kotlin-android plugin with com.android.feature

Add a test for Databinding (not passing yet with 3.0.0+, until
3.0.0-alpha4 is released, which contains the fix from the Android side).

Add missing test project android-databinding.

Refactor and improve the tests

* Remove redundant files from test project 'android-databinding'
* Refactoring and minor improvements in the tests
This commit is contained in:
Sergey Igushkin
2017-06-05 19:24:02 +03:00
parent 483218e491
commit 21b88995ab
12 changed files with 316 additions and 3 deletions
@@ -8,13 +8,39 @@ import java.io.File
class KotlinAndroidGradleIT : AbstractKotlinAndroidGradleTests(gradleVersion = "3.3", androidGradlePluginVersion = "2.3.0")
class KotlinAndroid30GradleIT : AbstractKotlinAndroidGradleTests(gradleVersion = "4.0-rc-1", androidGradlePluginVersion = "3.0.0-alpha2")
class KotlinAndroidWithJackGradleIT : AbstractKotlinAndroidWithJackGradleTests(gradleVersion = "3.3", androidGradlePluginVersion = "2.3.+")
class KotlinAndroid30GradleIT : AbstractKotlinAndroidGradleTests(gradleVersion = "4.0-rc-1", androidGradlePluginVersion = "3.0.0-alpha2") {
@Test
fun testApplyWithFeaturePlugin() {
val project = Project("AndroidProject", gradleVersion)
project.setupWorkingDir()
File(project.projectDir, "Lib/build.gradle").modify { text ->
// Change the applied plugin to com.android.feature
text.replace("com.android.library", "com.android.feature").apply { assert(!equals(text)) }
}
// Check that Kotlin tasks were created for both lib and feature variants:
val kotlinTaskNames =
listOf("Debug", "Release").flatMap { buildType ->
listOf("Flavor1", "Flavor2").flatMap { flavor ->
listOf("", "Feature").map { isFeature -> ":Lib:compile$flavor$buildType${isFeature}Kotlin" }
}
}
project.build(":Lib:assemble") {
assertSuccessful()
assertContains(*kotlinTaskNames.toTypedArray())
}
}
}
const val ANDROID_HOME_PATH = "../../../dependencies/androidSDK"
abstract class AbstractKotlinAndroidGradleTests(
private val gradleVersion: String,
protected val gradleVersion: String,
private val androidGradlePluginVersion: String
) : BaseGradleIT() {
@@ -6,7 +6,7 @@ import java.io.File
class Kapt3Android30IT : Kapt3AndroidIT() {
override val androidGradlePluginVersion: String
get() = "3.0.0-alpha1"
get() = "3.0.0-alpha3"
}
open class Kapt3AndroidIT : Kapt3BaseIT() {
@@ -106,4 +106,17 @@ open class Kapt3AndroidIT : Kapt3BaseIT() {
assertFileExists("build/generated/source/kapt/release/io/realm/DefaultRealmModuleMediator.java")
}
}
@Test
open fun testDatabinding() {
val project = Project("android-databinding", GRADLE_VERSION, directoryPrefix = "kapt2")
val options = androidBuildOptions()
project.build("compileReleaseSources", options = options) {
assertSuccessful()
assertKaptSuccessful()
assertFileExists("app/build/generated/source/kapt/release/com/example/databinding/BR.java")
assertFileExists("app/build/generated/source/kapt/release/com/example/databinding/databinding/ActivityTestBinding.java")
}
}
}
@@ -0,0 +1,36 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 24
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.example.databinding"
minSdkVersion 21
targetSdkVersion 24
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dataBinding {
enabled = true
}
}
repositories {
maven { url 'https://maven.google.com' }
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
kapt "com.android.databinding:compiler:$android_tools_version"
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.databinding">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,99 @@
package com.example.databinding
import android.content.Context
import android.content.res.Resources
import android.databinding.*
import android.databinding.adapters.AdapterViewBindingAdapter
import android.support.annotation.IntegerRes
import android.util.Log
import android.view.*
import android.widget.*
interface Displayable {
fun displayString(res: Resources): String
}
@InverseBindingMethods(
InverseBindingMethod(type = Spinner::class, attribute = "selectionEnum", method = "getSelectedItem", event = "app:selectionAttrChanged")
)
open class EnumAdapter<DispEnum>(val context: Context,
enumClass: Class<DispEnum>,
@IntegerRes var resource: Int = android.R.layout.simple_spinner_item,
@IntegerRes var dropDownResource: Int = android.R.layout.simple_spinner_dropdown_item,
val isOptional: Boolean = false) :
BaseAdapter() where DispEnum : Displayable, DispEnum : Enum<DispEnum>
{
val inflater: LayoutInflater = LayoutInflater.from(context)
val konstants: Array<DispEnum> = enumClass.enumConstants
override fun getItem(position: Int): DispEnum? {
if (isOptional) {
return if (position == 0) null else konstants[position - 1]
}
return konstants[position]
}
override fun getItemId(position: Int): Long = position + 0L
override fun getCount(): Int = konstants.size + if (isOptional) 1 else 0
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return createViewFromResource(position, convertView, parent, resource)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return createViewFromResource(position, convertView, parent, dropDownResource)
}
private fun createViewFromResource(position: Int, convertView: View?, parent: ViewGroup, resource: Int): View {
val view = convertView ?: inflater.inflate(resource, parent, false)
val textView: TextView
try {
textView = view as TextView
} catch (e: ClassCastException) {
Log.e("EnumAdapter", "You must supply a resource ID for a TextView")
return view
}
val enum = getItem(position)
textView.text = enum?.displayString(context.resources) ?: "-----"
return view
}
companion object {
@JvmStatic fun optionalPosition(e: Enum<*>?): Int {
e?.let { return it.ordinal + 1}
return 0
}
}
}
@BindingAdapter("app:selectionEnum", "app:selectionChanged", "app:selectionAttrChanged", requireAll = false)
fun bindSpinnerEnum(spinner: Spinner, newValue: Enum<*>?,
itemSelectedListener: AdapterViewBindingAdapter.OnItemSelected?,
changeListener: InverseBindingListener?) {
val spinnerAdapter = spinner.adapter
if (spinnerAdapter !is EnumAdapter<*>) {
throw UnsupportedOperationException("app:selectionEnum attribute on Spinner requires EnumAdapter")
}
if (changeListener == null && itemSelectedListener == null)
spinner.onItemSelectedListener = null
else
spinner.onItemSelectedListener = AdapterViewBindingAdapter.OnItemSelectedComponentListener(itemSelectedListener, nothingingSelectedListener, changeListener)
val isOptional = spinnerAdapter.isOptional
if (!isOptional && newValue == null) {
throw IllegalStateException("Can't set a null value for app:selectionEnum, adapter.isOptional is false")
}
if (spinner.selectedItem != newValue) {
val sel = if (isOptional) EnumAdapter.optionalPosition(newValue) else newValue!!.ordinal
spinner.setSelection(sel)
}
}
val nothingingSelectedListener = object : AdapterViewBindingAdapter.OnNothingSelected {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
@@ -0,0 +1,38 @@
package com.example.databinding
import android.app.Activity
import android.databinding.DataBindingUtil
import android.os.Bundle
import android.view.MotionEvent
import android.widget.Toast
import com.example.databinding.databinding.ActivityTestBinding
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityTestBinding = DataBindingUtil.setContentView(this, R.layout.activity_test)
val uprof = UserProfile()
binding.userProfile = uprof
binding.context = this
binding.genderPicker.adapter = EnumAdapter(this, Gender::class.java)
binding.genderPicker.setOnTouchListener { v, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN)
spinnerClicked = true
false
}
}
var spinnerClicked = false
fun spinnerClicked() {
spinnerClicked = true
}
fun selectionChanged() {
if (spinnerClicked)
Toast.makeText(this, "sdds", Toast.LENGTH_LONG).show()
spinnerClicked = false
}
fun clickHandler(uprof: UserProfile): Boolean = true
fun longClickHandler(uprof: UserProfile): Boolean = true
}
@@ -0,0 +1,13 @@
package com.example.databinding
import android.content.res.Resources
import android.databinding.BaseObservable
class UserProfile : BaseObservable() {
open var gender: Gender = Gender.Female
}
enum class Gender(val display: String): Displayable {
Male("male"), Female("female");
override fun displayString(res: Resources): String = display
}
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="userProfile" type="com.example.databinding.UserProfile" />
<variable name="context" type="com.example.databinding.MainActivity" />
</data>
<LinearLayout
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
android:onClick="@{() -> context.clickHandler(userProfile)}"
android:onLongClick="@{() -> context.longClickHandler(userProfile)}"
>
<Spinner
android:id="@+id/genderPicker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:selectionEnum="@={userProfile.gender}"
app:selectionChanged="@{(p, v, pos, id) -> context.selectionChanged()}"
/>
</LinearLayout>
</layout>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">ActivityRideUpTest</string>
</resources>
@@ -0,0 +1,17 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
<style name="EditProfileRow">
<item name="android:layout_marginBottom">18dp</item>
<item name="android:layout_column">0</item>
</style>
<style name="EditProfileRowColumn" parent="EditProfileRow">
<item name="android:layout_marginEnd">5dp</item>
</style>
</resources>
@@ -0,0 +1,24 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
mavenLocal()
maven { url 'https://maven.google.com' }
}
dependencies {
classpath "com.android.tools.build:gradle:$android_tools_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
mavenLocal()
maven { url 'https://maven.google.com' }
}
}