fix: add tests for MainFragment and refactor testing modules

This commit is contained in:
Akash Yadav
2023-10-07 23:37:34 +05:30
parent 3816ec5866
commit cc0a6391f6
193 changed files with 714 additions and 377 deletions
+39
View File
@@ -31,6 +31,45 @@
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-166137969">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Tests" value="360" />
<entry key="realme RMX3312" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-136582810">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Tests" value="360" />
<entry key="realme RMX3312" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1123912474">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Tests" value="360" />
<entry key="realme RMX3312" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
</map>
</option>
</component>
+12
View File
@@ -116,6 +116,7 @@
<module name="AndroidIDE.ksp-processor" target="11" />
<module name="AndroidIDE.ksp-processor.main" target="11" />
<module name="AndroidIDE.ksp-processor.test" target="11" />
<module name="AndroidIDE.lsp.testing" target="17" />
<module name="AndroidIDE.sdk-info" target="11" />
<module name="AndroidIDE.subprojects.classfile" target="11" />
<module name="AndroidIDE.subprojects.classfile.main" target="11" />
@@ -128,12 +129,23 @@
<module name="AndroidIDE.subprojects.tooling-api-model2" target="11" />
<module name="AndroidIDE.subprojects.tooling-api-model2.main" target="11" />
<module name="AndroidIDE.subprojects.tooling-api-model2.test" target="11" />
<module name="AndroidIDE.subprojects.tooling-api-testing" target="11" />
<module name="AndroidIDE.subprojects.tooling-api-testing.main" target="11" />
<module name="AndroidIDE.subprojects.tooling-api-testing.test" target="11" />
<module name="AndroidIDE.subprojects.xml-formatter.main" target="11" />
<module name="AndroidIDE.subprojects.xml-formatter.test" target="11" />
<module name="AndroidIDE.templates-testing" target="17" />
<module name="AndroidIDE.templates-testing.main" target="11" />
<module name="AndroidIDE.templates-testing.test" target="11" />
<module name="AndroidIDE.testing-android" target="17" />
<module name="AndroidIDE.testing.android.test" target="11" />
<module name="AndroidIDE.testing.common" target="11" />
<module name="AndroidIDE.testing.common.main" target="11" />
<module name="AndroidIDE.testing.common.test" target="11" />
<module name="AndroidIDE.testing.tooling-api" target="11" />
<module name="AndroidIDE.testing.tooling-api.main" target="11" />
<module name="AndroidIDE.testing.tooling-api.test" target="11" />
<module name="AndroidIDE.testing.unit.test" target="11" />
<module name="AndroidIDE.treesitter.test" target="1.7" />
</bytecodeTargetLevel>
</component>
+3 -7
View File
@@ -2,6 +2,7 @@
import androidx.navigation.safe.args.generator.ext.capitalize
import com.android.build.gradle.BaseExtension
import org.jetbrains.kotlin.gradle.plugin.extraProperties
import java.util.Base64
import java.util.Locale
@@ -162,13 +163,8 @@ dependencies {
// So we always copy the latest JAR file to assets
compileOnly(projects.subprojects.toolingApiImpl)
testImplementation(libs.common.editor)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.robolectric)
androidTestImplementation(libs.tests.androidx.junit)
androidTestImplementation(libs.tests.androidx.espresso)
androidTestImplementation(libs.tests.google.truth)
testImplementation(projects.testing.unit)
androidTestImplementation(projects.testing.android)
}
fun downloadSigningKey() {
@@ -0,0 +1,181 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.activities
import android.view.View
import android.view.ViewGroup
import androidx.cardview.widget.CardView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.R
import com.itsaky.androidide.models.MainScreenAction
import com.itsaky.androidide.templates.BooleanParameter
import com.itsaky.androidide.templates.EnumParameter
import com.itsaky.androidide.templates.ITemplateProvider
import com.itsaky.androidide.templates.StringParameter
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
import org.hamcrest.TypeSafeMatcher
import org.hamcrest.core.IsInstanceOf.instanceOf
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class MainFragmentTest {
@Rule
@JvmField
var mActivityScenarioRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun testMainFragmentActionsAreShown() {
val recyclerView = onView(allOf(
withId(R.id.actions),
childAtPosition(
withId(R.id.coordinator),
3)))
recyclerView.check(matches(isDisplayed()))
for (action in MainScreenAction.all()) {
val button = onView(
allOf(withText(action.text),
withParent(allOf(withId(R.id.actions),
withParent(withId(R.id.coordinator)))),
isDisplayed()))
button.check(matches(isDisplayed()))
}
}
@Test
fun testTemplateWizardUi() {
onView(withText(R.string.get_started)).check(matches(isCompletelyDisplayed()))
val recyclerView = onView(allOf(
withId(R.id.actions),
childAtPosition(
withId(R.id.coordinator),
3)))
recyclerView.check(matches(isDisplayed()))
val action = MainScreenAction.all().find { it.id == MainScreenAction.ACTION_CREATE_PROJECT }
assertThat(action).isNotNull()
action!!
val createProject = onView(allOf(withText(action.text), isDisplayed()))
createProject.check(matches(isCompletelyDisplayed()))
createProject.perform(click())
val exitButton = onView(
allOf(withId(R.id.exit_button), isCompletelyDisplayed())
)
exitButton.check(matches(isCompletelyDisplayed()))
val templates = ITemplateProvider.getInstance().getTemplates()
for (template in templates) {
val templateTitle = onView(
allOf(withId(R.id.template_name), withText(template.templateName),
withParent(
withParent(instanceOf(CardView::class.java))),
isCompletelyDisplayed()))
templateTitle.check(matches(isCompletelyDisplayed()))
}
val template = templates.first()
val firstTemplateItem = onView(
allOf(withId(R.id.template_name), withText(template.templateName),
withParent(
withParent(instanceOf(CardView::class.java))),
isCompletelyDisplayed()))
firstTemplateItem.perform(click())
val previousButton = onView(
allOf(withId(R.id.previous), withText(R.string.previous), isCompletelyDisplayed()))
val createProjectFinishButton = onView(
allOf(withId(R.id.finish), withText(R.string.create_project),
isCompletelyDisplayed()))
previousButton.check(matches(isCompletelyDisplayed()))
createProjectFinishButton.check(matches(isCompletelyDisplayed()))
for (parameter in template.parameters) {
val selector = when (parameter) {
is StringParameter -> withText(parameter.value)
is BooleanParameter -> allOf(withText(parameter.name),
if (parameter.value) isChecked() else isNotChecked())
is EnumParameter -> withText(parameter.getDisplayName())
else -> throw IllegalStateException("Unknown parameter type: $parameter")
}
val widget = onView(selector)
widget.check(matches(isCompletelyDisplayed()))
}
previousButton.perform(click())
Thread.sleep(1000L)
exitButton.check(matches(isCompletelyDisplayed()))
exitButton.perform(click())
Thread.sleep(1000L)
onView(withText(R.string.get_started)).check(matches(isCompletelyDisplayed()))
}
private fun childAtPosition(
parentMatcher: Matcher<View>, position: Int): Matcher<View> {
return object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("Child at position $position in parent ")
parentMatcher.describeTo(description)
}
public override fun matchesSafely(view: View): Boolean {
val parent = view.parent
return parent is ViewGroup && parentMatcher.matches(parent)
&& view == parent.getChildAt(position)
}
}
}
}
@@ -47,7 +47,9 @@ constructor(val actions: List<MainScreenAction> = emptyList()) :
binding.root.apply {
setText(action.text)
setIconResource(action.icon)
setOnClickListener(action.onClick)
setOnClickListener {
action.onClick?.invoke(action, it)
}
}
}
}
@@ -18,7 +18,6 @@ import com.itsaky.androidide.common.databinding.LayoutDialogProgressBinding
import com.itsaky.androidide.databinding.FragmentMainBinding
import com.itsaky.androidide.models.MainScreenAction
import com.itsaky.androidide.preferences.databinding.LayoutDialogTextInputBinding
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.tasks.executeAsyncProvideError
import com.itsaky.androidide.utils.DialogUtils
@@ -40,7 +39,7 @@ class MainFragment : BaseFragment() {
private val log = ILogger.newInstance("MainFragment")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
savedInstanceState: Bundle?
): View {
binding = FragmentMainBinding.inflate(inflater, container, false)
return binding!!.root
@@ -48,28 +47,26 @@ class MainFragment : BaseFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val createProject = MainScreenAction(string.create_project,
R.drawable.ic_add) { showCreateProject() }
val openProject = MainScreenAction(string.msg_open_existing_project,
R.drawable.ic_folder) { pickDirectory() }
val cloneGitRepository = MainScreenAction(string.git_clone_repo,
R.drawable.ic_git) { cloneGitRepo() }
val openTerminal =
MainScreenAction(string.title_terminal, R.drawable.ic_terminal) {
startActivity(Intent(requireActivity(), TerminalActivity::class.java))
val actions = MainScreenAction.all().also { actions ->
val onClick = { action: MainScreenAction, _: View ->
when (action.id) {
MainScreenAction.ACTION_CREATE_PROJECT -> showCreateProject()
MainScreenAction.ACTION_OPEN_PROJECT -> pickDirectory()
MainScreenAction.ACTION_CLONE_REPO -> cloneGitRepo()
MainScreenAction.ACTION_OPEN_TERMINAL -> startActivity(
Intent(requireActivity(), TerminalActivity::class.java))
MainScreenAction.ACTION_PREFERENCES -> gotoPreferences()
MainScreenAction.ACTION_DONATE -> BaseApplication.getBaseInstance().openDonationsPage()
MainScreenAction.ACTION_DOCS -> BaseApplication.getBaseInstance().openDocs()
}
}
val preferences = MainScreenAction(string.msg_preferences,
R.drawable.ic_settings) { gotoPreferences() }
val sponsor = MainScreenAction(string.btn_donate, R.drawable.ic_donate) {
BaseApplication.getBaseInstance().openSponsors()
}
val docs = MainScreenAction(string.btn_docs, R.drawable.ic_docs) {
BaseApplication.getBaseInstance().openDocs()
actions.forEach { action -> action.onClick = onClick }
}
binding!!.actions.adapter = MainActionsListAdapter(
listOf(createProject, openProject, cloneGitRepository, openTerminal,
preferences, docs, sponsor))
binding!!.actions.adapter = MainActionsListAdapter(actions)
}
override fun onDestroyView() {
@@ -182,7 +179,7 @@ class MainFragment : BaseFragment() {
// TODO(itsaky) : Improve this implementation
class GitCloneProgressMonitor(val progress: LinearProgressIndicator,
val message: TextView
val message: TextView
) : ProgressMonitor {
private var cancelled = false
@@ -20,6 +20,8 @@ package com.itsaky.androidide.models
import android.view.View
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.itsaky.androidide.resources.R
import java.util.Collections
/**
* An action button shown on the main screen.
@@ -28,4 +30,75 @@ import androidx.annotation.StringRes
*/
data class MainScreenAction
@JvmOverloads
constructor(@StringRes val text: Int, @DrawableRes val icon: Int, val onClick: (View) -> Unit = {})
constructor(
val id: Int,
@StringRes val text: Int,
@DrawableRes val icon: Int,
var onClick: ((MainScreenAction, View) -> Unit)? = null
) {
companion object {
const val ACTION_CREATE_PROJECT = 0
const val ACTION_OPEN_PROJECT = 1
const val ACTION_CLONE_REPO = 2
const val ACTION_OPEN_TERMINAL = 3
const val ACTION_PREFERENCES = 4
const val ACTION_DONATE = 5
const val ACTION_DOCS = 6
/**
* Get all main screen actions.
*/
@JvmStatic
fun all(): List<MainScreenAction> {
return mutableListOf<MainScreenAction>().apply {
val createProject = MainScreenAction(
ACTION_CREATE_PROJECT,
R.string.create_project,
R.drawable.ic_add)
val openProject = MainScreenAction(
ACTION_OPEN_PROJECT,
R.string.msg_open_existing_project,
R.drawable.ic_folder)
val cloneGitRepository = MainScreenAction(
ACTION_CLONE_REPO,
R.string.git_clone_repo,
R.drawable.ic_git)
val openTerminal = MainScreenAction(
ACTION_OPEN_TERMINAL,
R.string.title_terminal,
R.drawable.ic_terminal)
val preferences = MainScreenAction(
ACTION_PREFERENCES,
R.string.msg_preferences,
R.drawable.ic_settings)
val donate = MainScreenAction(
ACTION_DONATE,
R.string.btn_donate,
R.drawable.ic_donate
)
val docs = MainScreenAction(
ACTION_DOCS,
R.string.btn_docs,
R.drawable.ic_docs)
Collections.addAll(this,
createProject,
openProject,
cloneGitRepository,
openTerminal,
preferences,
donate,
docs
)
}
}
}
}
@@ -134,7 +134,7 @@ public class BaseApplication extends Application {
openUrl(BuildInfo.PROJECT_SITE);
}
public void openSponsors() {
public void openDonationsPage() {
openUrl(SPONSOR_URL);
}
+4 -1
View File
@@ -13,6 +13,7 @@ androidx-navigation = "2.5.3"
ksp = "1.9.10-1.0.13"
antlr4 = "4.13.1"
androidx-work = "2.8.1"
androidx-espresso = "3.5.1"
retrofit = "2.9.0"
[libraries]
@@ -113,7 +114,9 @@ tests-androidx-test-runner = { module = "androidx.test:runner", version = "1.5.2
tests-androidx-test-rules = { module = "androidx.test:rules", version = "1.5.0" }
tests-androidx-junit = { module = "androidx.test.ext:junit", version = "1.1.5" }
tests-androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "androidx-work" }
tests-androidx-espresso = { module = "androidx.test.espresso:espresso-core", version = "3.5.1" }
tests-androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" }
tests-androidx-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "androidx-espresso" }
tests-androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.2.0" }
tests-mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "5.1.0" }
tests-mockk = { module = "io.mockk:mockk", version = "1.13.8" }
+2 -11
View File
@@ -58,15 +58,6 @@ dependencies {
implementation(libs.androidx.ktx)
implementation(libs.common.kotlin)
testImplementation(projects.lsp.testing)
testImplementation(projects.common)
testImplementation(projects.actions)
testImplementation(projects.subprojects.javac)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.robolectric)
androidTestImplementation(libs.tests.androidx.junit)
androidTestImplementation(libs.tests.androidx.espresso)
androidTestImplementation(libs.tests.google.truth)
testImplementation(projects.testing.lsp)
}
+4 -13
View File
@@ -49,21 +49,12 @@ dependencies {
implementation(libs.androidx.ktx)
implementation(libs.common.kotlin)
implementation(libs.google.material)
testImplementation(projects.actions)
testImplementation(projects.subprojects.projects)
testImplementation(projects.subprojects.toolingApi)
testImplementation(projects.lsp.testing)
testImplementation(projects.common)
testImplementation(projects.actions)
testImplementation(libs.tests.androidx.test.core)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.robolectric)
androidTestImplementation(libs.tests.androidx.junit)
androidTestImplementation(libs.tests.androidx.espresso)
androidTestImplementation(libs.tests.google.truth)
testImplementation(projects.testing.lsp)
compileOnly(projects.common)
compileOnly(libs.common.antlr4)
}
+5 -3
View File
@@ -73,7 +73,6 @@ include(
":lsp:models",
":lsp:java",
":lsp:xml",
":lsp:testing",
":subprojects:aaptcompiler",
":subprojects:builder-model-impl",
":subprojects:flashbar",
@@ -94,7 +93,10 @@ include(
":subprojects:tooling-api-events",
":subprojects:tooling-api-impl",
":subprojects:tooling-api-model",
":subprojects:tooling-api-testing",
":subprojects:xml-dom",
":subprojects:xml-utils"
":subprojects:xml-utils",
":testing:android",
":testing:lsp",
":testing:tooling",
":testing:unit",
)
@@ -62,13 +62,16 @@ class FileProvider {
}
@JvmStatic
private fun testsDir(): Path = projectRoot().resolve("tests")
private fun testingDir() = projectRoot().resolve("testing")
@JvmStatic
fun testHomeDir(): Path = testsDir().resolve("test-home")
private fun testReourcessDir(): Path = testingDir().resolve("resources")
@JvmStatic
fun testProjectRoot(): Path = testsDir().resolve("test-project")
fun testHomeDir(): Path = testReourcessDir().resolve("test-home")
@JvmStatic
fun testProjectRoot(): Path = testReourcessDir().resolve("test-project")
/**
* Get the path to the 'resources' directory.
@@ -0,0 +1,23 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.utils
@Suppress("UNCHECKED_CAST")
fun <T> uncheckedCast(obj: Any): T {
return obj as T
}
+1 -8
View File
@@ -36,12 +36,5 @@ dependencies {
implementation(libs.google.auto.service.annotations)
implementation(libs.google.guava)
testImplementation(projects.subprojects.toolingApiTesting)
testImplementation(projects.shared)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.robolectric)
androidTestImplementation(libs.tests.androidx.junit)
androidTestImplementation(libs.tests.androidx.espresso)
androidTestImplementation(libs.tests.google.truth)
testImplementation(projects.testing.tooling)
}
@@ -55,19 +55,17 @@ project.tasks.getByName("jar") {
project.tasks.getByName("shadowJar") { finalizedBy("copyJar") }
dependencies {
api(projects.subprojects.toolingApi)
implementation(projects.buildInfo)
implementation(projects.shared)
implementation(projects.subprojects.toolingApi)
implementation(libs.common.jkotlin)
implementation(libs.xml.xercesImpl)
implementation(libs.xml.apis)
implementation(libs.tooling.gradleApi)
testImplementation(projects.subprojects.toolingApiTesting)
testImplementation(projects.shared)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
testImplementation(projects.testing.tooling)
runtimeOnly(libs.tooling.slf4j)
}
@@ -1,73 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
plugins {
id 'groovy-gradle-plugin'
id 'maven-publish'
}
group "com.itsaky.androidide"
version "1.0.0"
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
publishing {
repositories {
maven {
url new File (project.buildDir, "repo")
}
}
}
gradlePlugin {
plugins {
androidide {
id = "com.itsaky.androidide"
implementationClass = "com.itsaky.androidide.tooling.plugin.AndroidIDEToolingApiPlugin"
}
}
}
task copyToTestDir (type: Copy) {
from "$project.buildDir.absolutePath/libs/tooling-api-plugin-${version}.jar"
into "${project.rootProject.file ("tests/test-home/.androidide/init").absolutePath}/"
rename {
"plugin.jar"
}
outputs.upToDateWhen {
false
}
}
project.tasks.jar {
finalizedBy project.tasks.publish
}
project.tasks.publish {
finalizedBy copyToTestDir
outputs.upToDateWhen {
false
}
}
dependencies {
implementation project (path: ':subprojects:tooling-api-model')
}
@@ -1,73 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.tooling.plugin;
import com.itsaky.androidide.tooling.api.model.IdeProject;
import com.itsaky.androidide.tooling.api.model.ProjectDependency;
import com.itsaky.androidide.tooling.api.model.internal.DefaultIdeProject;
import org.gradle.api.Project;
import org.gradle.tooling.provider.model.ToolingModelBuilder;
import java.util.ArrayList;
import java.util.List;
/**
* @author Akash Yadav
*/
public class IdeProjectModelBuilder implements ToolingModelBuilder {
@Override
public boolean canBuild(String s) {
return IdeProject.class.getName().equals(s);
}
@Override
public Object buildAll(String s, Project project) {
final var ideProject = new DefaultIdeProject();
fillProjectDetails(project, ideProject);
return ideProject;
}
private void fillProjectDetails(Project project, DefaultIdeProject ideProject) {
ideProject.setPath(project.getPath());
ideProject.setDisplayName(project.getDisplayName());
ideProject.setProjectDir(project.getProjectDir());
ideProject.setBuildDir(project.getBuildDir());
ideProject.setBuildFile(project.getBuildFile());
ideProject.setDependencies(getDependencies(project));
ideProject.setSubProjects(getSubProjects(project));
}
private List<ProjectDependency> getDependencies(Project project) {
return null;
}
private List<IdeProject> getSubProjects(Project project) {
final var projects = new ArrayList<IdeProject>();
project
.getSubprojects()
.forEach(
sub -> {
final var subProject = new DefaultIdeProject();
fillProjectDetails(sub, subProject);
projects.add(subProject);
});
return projects;
}
}
-3
View File
@@ -30,7 +30,4 @@ dependencies {
api(libs.google.gson)
api(libs.common.jsonrpc)
implementation(libs.common.jkotlin)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
}
@@ -323,7 +323,15 @@ class EnumParameter<T : Enum<*>>(@StringRes name: Int,
val displayName: ((T) -> String)? = null,
val filter: ((T) -> Boolean)? = null
) : TextFieldParameter<T>(name, description, default, startIcon, endIcon,
onStartIconClick, onEndIconClick, constraints)
onStartIconClick, onEndIconClick, constraints) {
/**
* Get the display name for this [EnumParameter].
*/
fun getDisplayName(): String? {
return this.displayName?.invoke(value)
}
}
class EnumParameterBuilder<T : Enum<*>> : TextFieldParameterBuilder<T>() {
+2 -8
View File
@@ -38,15 +38,9 @@ dependencies {
implementation(libs.androidx.ktx)
implementation(libs.google.auto.service.annotations)
testImplementation(projects.common)
testImplementation(projects.templatesApi)
testImplementation(projects.lsp.api)
testImplementation(projects.preferences)
testImplementation(projects.shared)
testImplementation(projects.subprojects.toolingApiTesting)
testImplementation(libs.tests.androidx.test.core)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.mockk)
testImplementation(libs.tests.robolectric)
testImplementation(projects.testing.unit)
testImplementation(projects.testing.tooling)
}
@@ -15,29 +15,28 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.tooling.plugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
import javax.inject.Inject;
public class AndroidIDEToolingApiPlugin implements Plugin<Project> {
private final ToolingModelBuilderRegistry registry;
@Inject
public AndroidIDEToolingApiPlugin(ToolingModelBuilderRegistry registry) {
this.registry = registry;
}
@Override
public void apply(Project project) {
if (project != project.getRootProject()) {
return;
}
registry.register(new IdeProjectModelBuilder());
}
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
id("com.android.library")
kotlin("android")
}
android {
namespace = "${BuildConfig.packageName}.testing.android"
}
dependencies {
api(libs.tests.androidx.test.core)
api(libs.tests.androidx.test.runner)
api(libs.tests.androidx.test.rules)
api(libs.tests.androidx.junit)
api(libs.tests.androidx.espresso.core)
api(libs.tests.androidx.espresso.contrib)
api(libs.tests.androidx.uiautomator)
api(libs.tests.junit)
api(libs.tests.google.truth)
api(projects.buildInfo)
api(projects.common)
api(projects.shared)
}
View File
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -15,27 +15,14 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.projects
package com.itsaky.androidide.testing.android
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
import android.content.Context
import androidx.annotation.StringRes
import androidx.test.core.app.ApplicationProvider
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
* Get the value of the string resource.
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.itsaky.androidide.projects.test", appContext.packageName)
}
}
fun stringRes(@StringRes resource: Int, vararg formatArgs: Any) =
ApplicationProvider.getApplicationContext<Context>().getString(resource, *formatArgs)
@@ -0,0 +1,93 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.testing.android
import android.app.Activity
import android.content.Intent
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import androidx.test.runner.lifecycle.Stage
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.app.BaseApplication
import com.itsaky.androidide.buildinfo.BuildInfo
import com.itsaky.androidide.utils.uncheckedCast
const val LAUNCH_TIMEOUT = 5000L
/**
* Launches the AndroidIDE application using UI Automator.
*
* @param fromLauncher Whether the application must be launched from the Android launcher.
* @param clearTasks Whether the application's previous tasks must be cleared.
*/
fun launchAndroidIDE(
fromLauncher: Boolean = true,
clearTasks: Boolean = true,
): UiDevice {
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
if (fromLauncher) {
device.pressHome()
val launcherPackage = device.launcherPackageName
assertThat(launcherPackage).isNotNull()
device.wait(
Until.hasObject(By.pkg(launcherPackage).depth(0)),
LAUNCH_TIMEOUT
)
}
// Launch the app
val context = ApplicationProvider.getApplicationContext<BaseApplication>()
val intent = context.packageManager.getLaunchIntentForPackage(BuildInfo.PACKAGE_NAME)
assertThat(intent).isNotNull()
if (clearTasks) {
intent!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
context.startActivity(intent)
// Wait for the app to appear
device.wait(
Until.hasObject(By.pkg(BuildInfo.PACKAGE_NAME).depth(0)),
LAUNCH_TIMEOUT
)
return device
}
/**
* Get the first activity in the given [stage].
*/
fun <T : Activity> getActivityInStage(stage: Stage): T? {
var activity: T? = null
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(stage)
if (activities.isNotEmpty()) {
activity = uncheckedCast(activities.first())
}
}
return activity
}
@@ -0,0 +1,54 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.testing.android
import androidx.annotation.StringRes
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import com.itsaky.androidide.buildinfo.BuildInfo
/**
* @see UiDevice.waitForWindowUpdate
*/
fun UiDevice.waitForWindowUpdate(timeout: Long) =
waitForWindowUpdate(BuildInfo.PACKAGE_NAME, timeout)
/**
* @see UiDevice.hasObject
*/
fun UiDevice.hasObjectWithText(text: String) =
hasObject(By.text(text))
/**
* @see UiDevice.hasObject
*/
fun UiDevice.hasObjectWithText(@StringRes text: Int) =
hasObject(By.text(stringRes(text)))
/**
* @see UiDevice.findObject
*/
fun UiDevice.findObjectWithText(text: String): UiObject2? =
findObject(By.text(text))
/**
* @see UiDevice.findObjectWithText
*/
fun UiDevice.findObjectWithText(@StringRes text: Int): UiObject2? =
findObject(By.text(stringRes(text)))
@@ -0,0 +1,46 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.testing.android
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.google.common.truth.Truth.assertThat
/**
* Click this object and wait for window update with the given timeout duration.
*
* @see UiDevice.waitForWindowUpdate
*/
fun UiObject2?.clickAndWaitForWindowUpdate(device: UiDevice, timeout: Long = 500L) =
run {
assertThat(this).isNotNull()
this!!.click()
device.waitForWindowUpdate(timeout)
}
/**
* Click this object and wait for a new window to appear within the given timeout duration.
*
* @see UiObject2.clickAndWait
*/
fun UiObject2?.clickAndWaitForNewWindow(timeout: Long = LAUNCH_TIMEOUT) =
run {
assertThat(this).isNotNull()
this!!.clickAndWait(Until.newWindow(), timeout)
}
@@ -25,19 +25,14 @@ android {
}
dependencies {
implementation(libs.androidx.ktx)
implementation(libs.common.kotlin)
api(libs.androidx.ktx)
api(libs.common.kotlin)
implementation(projects.lsp.api)
implementation(projects.actions)
implementation(projects.common)
implementation(projects.eventbusAndroid)
implementation(projects.subprojects.toolingApiTesting)
implementation(projects.shared)
api(projects.lsp.api)
api(projects.actions)
api(projects.eventbusAndroid)
implementation(libs.common.editor)
implementation(libs.tests.junit)
implementation(libs.tests.google.truth)
implementation(libs.tests.robolectric)
implementation(libs.tests.mockk)
api(libs.common.editor)
api(projects.testing.tooling)
api(projects.testing.unit)
}

Some files were not shown because too many files have changed in this diff Show More