diff --git a/tests/test-home/.androidide/init/model.jar b/tests/test-home/.androidide/init/model.jar index 4601d2708..f97ab5f43 100644 Binary files a/tests/test-home/.androidide/init/model.jar and b/tests/test-home/.androidide/init/model.jar differ diff --git a/tests/test-project/app/src/main/res/layout/AbsoluteLayout.xml b/tests/test-project/app/src/main/res/layout/AbsoluteLayout.xml new file mode 100644 index 000000000..d54cfe847 --- /dev/null +++ b/tests/test-project/app/src/main/res/layout/AbsoluteLayout.xml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt index 39205ffc6..982cc6e8a 100644 --- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt +++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt @@ -43,8 +43,10 @@ import com.itsaky.androidide.inflater.internal.utils.IDTable import com.itsaky.androidide.projects.ProjectManager import com.itsaky.androidide.projects.api.AndroidModule import com.itsaky.androidide.utils.ILogger +import com.itsaky.androidide.utils.VMUtils import com.itsaky.androidide.xml.widgets.Widget import com.itsaky.androidide.xml.widgets.WidgetTable +import org.jetbrains.annotations.TestOnly import java.io.File import java.lang.reflect.Method @@ -64,7 +66,7 @@ open class LayoutInflaterImpl : ILayoutInflater() { get() = this._primaryInflatingFile!! protected val currentLayoutFile: LayoutFile - get() = _currentLayoutFile!! + get() = this._currentLayoutFile!! override fun inflate(file: File, parent: ViewGroup): IView? { this._primaryInflatingFile = file @@ -167,11 +169,11 @@ open class LayoutInflaterImpl : ILayoutInflater() { val adapter = AttributeAdapterIndex.getAdapter(view.name) ?: throw InflateException("No attribute adapter found for view ${view.name}") - - adapter.applyBasic(view) - + view.view.layoutParams = generateLayoutParams(parentView) parent.addChild(view) + + adapter.applyBasic(view) if (element.childCount > 0 && view is IViewGroup) { for (child in element.childList) { diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewImpl.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewImpl.kt index 6bfcbafaa..36214fdf1 100644 --- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewImpl.kt +++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/ViewImpl.kt @@ -35,7 +35,7 @@ constructor( override val tag: String = name.tagName() ) : IView { private val log = ILogger.newInstance(javaClass.simpleName) - protected val attributes = mutableListOf() + internal val attributes = mutableListOf() override var parent: IViewGroup? = null diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt index c19a7e7b4..0b4eb93f5 100644 --- a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt +++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt @@ -76,7 +76,7 @@ open class ViewAttrAdapter : IAttributeAdapter() { "foregroundGravity" -> foregroundGravity = parseGravity(value) "foregroundTint" -> foregroundTintList = parseColorStateList(context, value) "foregroundTintMode" -> foregroundTintMode = parsePorterDuffMode(value) - "id" -> parseId(file.resName, value) + "id" -> id = parseId(file.resName, value) "minHeight" -> minimumWidth = parseDimension(context, value) "minWidth" -> minimumHeight = parseDimension(context, value) "padding" -> diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/AdapterIndexText.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/AdapterIndexText.kt index c4669bf7c..ce963244f 100644 --- a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/AdapterIndexText.kt +++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/AdapterIndexText.kt @@ -51,59 +51,21 @@ import com.itsaky.androidide.inflater.internal.adapters.WebViewAttrAdapter import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +import java.io.File /** @author Akash Yadav */ @RunWith(RobolectricTestRunner::class) class AdapterIndexText { - + @Test fun `verify instances of adapters`() { - val expected = - mapOf( - AbsoluteLayout::class to AbsoluteLayoutAttrAdapter::class, - AutoCompleteTextView::class to AutoCompleteTextViewAttrAdapter::class, - Button::class to ButtonAttrAdapter::class, - CheckBox::class to CheckBoxAttrAdapter::class, - CheckedTextView::class to CheckedTextViewAttrAdapter::class, - EditText::class to EditTextAttrAdapter::class, - FrameLayout::class to FrameLayoutAttrAdapter::class, - GestureOverlayView::class to GestureOverlayViewAttrAdapter::class, - GridLayout::class to GridLayoutAttrAdapter::class, - ImageButton::class to ImageButtonAttrAdapter::class, - ImageView::class to ImageViewAttrAdapter::class, - LinearLayout::class to LinearLayoutAttrAdapter::class, - ListView::class to ListViewAttrAdapter::class, - ProgressBar::class to ProgressBarAttrAdapter::class, - RadioButton::class to RadioButtonAttrAdapter::class, - RelativeLayout::class to RelativeLayoutAttrAdapter::class, - SeekBar::class to SeekBarAttrAdapter::class, - Spinner::class to SpinnerAttrAdapter::class, - Switch::class to SwitchAttrAdapter::class, - SurfaceView::class to SurfaceViewAttrAdapter::class, - TextView::class to TextViewAttrAdapter::class, - TextureView::class to TextureViewAttrAdapter::class, - ToggleButton::class to ToggleButtonAttrAdapter::class, - View::class to ViewAttrAdapter::class, - WebView::class to WebViewAttrAdapter::class - ) - - val unexpected = - listOf( - AbsListView::class, - AbsSeekBar::class, - AbsSpinner::class, - AdapterView::class, - CompoundButton::class, - ViewGroup::class - ) - - expected.forEach { + viewToAdapter.forEach { AttributeAdapterIndex.getAdapter(it.key.qualifiedName).apply { assertThat(this).isNotNull() assertThat(this).isInstanceOf(it.value.java) } } - unexpected.forEach { assertThat(AttributeAdapterIndex.getAdapter(it.qualifiedName)).isNull() } + abstractViews.forEach { assertThat(AttributeAdapterIndex.getAdapter(it.qualifiedName)).isNull() } } } diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/LayoutInflaterTest.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/LayoutInflaterTest.kt index 2ebd6e74f..9f1b065c6 100644 --- a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/LayoutInflaterTest.kt +++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/LayoutInflaterTest.kt @@ -19,25 +19,22 @@ package com.itsaky.androidide.inflater import android.view.View import android.widget.LinearLayout -import androidx.appcompat.app.AppCompatActivity import com.google.common.truth.Truth.assertThat -import com.itsaky.androidide.lookup.Lookup +import com.itsaky.androidide.inflater.internal.AttributeImpl +import com.itsaky.androidide.inflater.internal.LayoutInflaterImpl +import com.itsaky.androidide.inflater.internal.ViewImpl +import com.itsaky.androidide.inflater.internal.utils.IDTable import com.itsaky.androidide.projects.ProjectManager -import com.itsaky.androidide.projects.builder.BuildService -import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage -import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher +import com.itsaky.androidide.projects.api.AndroidModule import java.io.File -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner -import org.robolectric.android.controller.ActivityController /** @author Akash Yadav */ @RunWith(RobolectricTestRunner::class) class LayoutInflaterTest { - + @Test fun `test functionality`() { inflaterTest { @@ -53,7 +50,7 @@ class LayoutInflaterTest { } } } - + @Test fun `test inlined layout`() { inflaterTest { @@ -70,8 +67,46 @@ class LayoutInflaterTest { } } + @Test + fun `test inflated view is of expected type`() { + inflaterTest { module -> + requiresActivity { activity -> + val parent = LinearLayout(activity) + val inflater = ILayoutInflater.newInflater() as LayoutInflaterImpl + viewToAdapter.keys.forEach { view -> + parent.removeAllViews() + module.createLayoutFile(view.simpleName!!) { file -> + file.writeText(viewDeclTemplate(view.simpleName!!)) + val inflated = inflater.inflate(file, parent) + assertThat(inflated!!.view).isInstanceOf(view.java) + assertThat(inflated.view.id) + .isEqualTo(IDTable.get(view.simpleName!!, "template_view")) + + (inflated as ViewImpl).attributes.apply { + assertThat(this) + .contains(AttributeImpl(INamespace.ANDROID, "id", "@+id/template_view")) + assertThat(this) + .contains(AttributeImpl(INamespace.ANDROID, "layout_height", "match_parent")) + assertThat(this) + .contains(AttributeImpl(INamespace.ANDROID, "layout_width", "match_parent")) + } + } + } + } + } + } + private fun layoutFile(name: String): File { val app = ProjectManager.app ?: throw IllegalStateException("Project is not initialized") return File(app.projectDir, "src/main/res/layout/$name.xml") } + + private fun AndroidModule.createLayoutFile(name: String, block: (File) -> Unit = {}): File { + return File(projectDir, "src/main/res/layout/$name.xml").apply { + if (exists()) { + delete() + } + block(this) + } + } } diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/adapterUtils.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/adapterUtils.kt new file mode 100644 index 000000000..5ce767aa7 --- /dev/null +++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/adapterUtils.kt @@ -0,0 +1,124 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.inflater + +import android.gesture.GestureOverlayView +import android.view.SurfaceView +import android.view.TextureView +import android.view.View +import android.view.ViewGroup +import android.webkit.WebView +import android.widget.AbsListView +import android.widget.AbsSeekBar +import android.widget.AbsSpinner +import android.widget.AbsoluteLayout +import android.widget.AdapterView +import android.widget.AutoCompleteTextView +import android.widget.Button +import android.widget.CheckBox +import android.widget.CheckedTextView +import android.widget.CompoundButton +import android.widget.EditText +import android.widget.FrameLayout +import android.widget.GridLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.ListView +import android.widget.ProgressBar +import android.widget.RadioButton +import android.widget.RelativeLayout +import android.widget.SeekBar +import android.widget.Spinner +import android.widget.Switch +import android.widget.TextView +import android.widget.ToggleButton +import com.itsaky.androidide.inflater.internal.adapters.AbsoluteLayoutAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.AutoCompleteTextViewAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.ButtonAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.CheckBoxAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.CheckedTextViewAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.EditTextAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.FrameLayoutAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.GestureOverlayViewAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.GridLayoutAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.ImageButtonAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.ImageViewAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.LinearLayoutAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.ListViewAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.ProgressBarAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.RadioButtonAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.RelativeLayoutAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.SeekBarAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.SpinnerAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.SurfaceViewAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.SwitchAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.TextViewAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.TextureViewAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.ToggleButtonAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.ViewAttrAdapter +import com.itsaky.androidide.inflater.internal.adapters.WebViewAttrAdapter + +internal fun viewDeclTemplate(name: String) : String = + """ + <$name + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/template_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + """.trimIndent() + +internal val viewToAdapter = + mapOf( + AbsoluteLayout::class to AbsoluteLayoutAttrAdapter::class, + AutoCompleteTextView::class to AutoCompleteTextViewAttrAdapter::class, + Button::class to ButtonAttrAdapter::class, + CheckBox::class to CheckBoxAttrAdapter::class, + CheckedTextView::class to CheckedTextViewAttrAdapter::class, + EditText::class to EditTextAttrAdapter::class, + FrameLayout::class to FrameLayoutAttrAdapter::class, + GestureOverlayView::class to GestureOverlayViewAttrAdapter::class, + GridLayout::class to GridLayoutAttrAdapter::class, + ImageButton::class to ImageButtonAttrAdapter::class, + ImageView::class to ImageViewAttrAdapter::class, + LinearLayout::class to LinearLayoutAttrAdapter::class, + ListView::class to ListViewAttrAdapter::class, + ProgressBar::class to ProgressBarAttrAdapter::class, + RadioButton::class to RadioButtonAttrAdapter::class, + RelativeLayout::class to RelativeLayoutAttrAdapter::class, + SeekBar::class to SeekBarAttrAdapter::class, + Spinner::class to SpinnerAttrAdapter::class, + Switch::class to SwitchAttrAdapter::class, + SurfaceView::class to SurfaceViewAttrAdapter::class, + TextView::class to TextViewAttrAdapter::class, + TextureView::class to TextureViewAttrAdapter::class, + ToggleButton::class to ToggleButtonAttrAdapter::class, + View::class to ViewAttrAdapter::class, + WebView::class to WebViewAttrAdapter::class + ) + +internal val abstractViews = + listOf( + AbsListView::class, + AbsSeekBar::class, + AbsSpinner::class, + AdapterView::class, + CompoundButton::class, + ViewGroup::class + ) \ No newline at end of file