mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
build: reorganize and reconfigure modules to resolve dependency resolution errors
Gradle, for some reason, does not the path of modules to identify them uniquely. Instead, the 'build id' is used to do so, which consists of the maven group and artifact ID. So, even if two modules are located in separate directories, but have the name group and artifact ID, including both of them in other modules may result in dependency resolution errors.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import com.itsaky.androidide.build.config.BuildConfig
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
groupConfig {
|
||||
groupIdSuffix.set("testing")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "${BuildConfig.packageName}.testing.android"
|
||||
}
|
||||
|
||||
configurations.api {
|
||||
// There is a transitive dependency on 'protobuf-lite' in 'androidTest' configurations
|
||||
// which conflicts with the 'protobuf-javalite' library we use
|
||||
// hence we exclude the dependency and add ours manually
|
||||
//+--- androidx.test.espresso:espresso-core:<...>
|
||||
// +--- com.google.android.material:material:<...>
|
||||
// \--- com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:<...>
|
||||
// +--- com.google.protobuf:protobuf-lite:3.0.1
|
||||
exclude(group = "com.google.protobuf", module = "protobuf-lite")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// compileOnly so that we can provide the APIs for the related test rules
|
||||
// if a module needs to use those APIs, it needs to explicitly add the database module as a
|
||||
// dependency
|
||||
compileOnly(projects.core.database)
|
||||
|
||||
api(libs.google.protobuf)
|
||||
api(libs.tests.androidx.espresso.core)
|
||||
api(libs.tests.androidx.espresso.contrib)
|
||||
api(libs.tests.androidx.junit)
|
||||
api(libs.tests.androidx.test.core)
|
||||
api(libs.tests.androidx.test.runner)
|
||||
api(libs.tests.androidx.test.rules)
|
||||
api(libs.tests.androidx.uiautomator)
|
||||
api(libs.tests.junit)
|
||||
api(libs.tests.google.truth)
|
||||
api(libs.tests.barista) {
|
||||
exclude("org.jetbrains.kotlin")
|
||||
}
|
||||
|
||||
api(projects.core.common)
|
||||
api(projects.testing.commonTest)
|
||||
api(projects.utilities.buildInfo)
|
||||
api(projects.utilities.shared)
|
||||
}
|
||||
+21
@@ -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
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 androidx.test.uiautomator;
|
||||
|
||||
import android.app.UiAutomation;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class UiDeviceAccessor {
|
||||
|
||||
/**
|
||||
* Get the {@link UiAutomation} instance from the given {@link UiDevice}.
|
||||
*/
|
||||
public static UiAutomation getUiAutomation(UiDevice device) {
|
||||
return device.getUiAutomation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link InteractionController} instance from the given {@link UiDevice}.
|
||||
*/
|
||||
public static InteractionController getInteractionController(UiDevice device) {
|
||||
return device.getInteractionController();
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
|
||||
/**
|
||||
* Get the application context.
|
||||
*/
|
||||
fun <AppContext : Context> getApplicationContext(): AppContext =
|
||||
ApplicationProvider.getApplicationContext()
|
||||
|
||||
/**
|
||||
* Get the value of the string resource.
|
||||
*/
|
||||
fun stringRes(@StringRes resource: Int, vararg formatArgs: Any) =
|
||||
ApplicationProvider.getApplicationContext<Context>().getString(resource, *formatArgs)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.rules
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import org.junit.rules.TestRule
|
||||
|
||||
/**
|
||||
* Abstract class for Android testing rules.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class AbstractAndroidTestRule : TestRule {
|
||||
init {
|
||||
Environment.init(ApplicationProvider.getApplicationContext())
|
||||
}
|
||||
|
||||
protected val context: Context
|
||||
get() = ApplicationProvider.getApplicationContext()
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.rules
|
||||
|
||||
import com.itsaky.androidide.db.IRealmProvider
|
||||
import com.itsaky.androidide.testing.android.util.NoOpStatement
|
||||
import io.realm.Realm
|
||||
import io.realm.log.LogLevel
|
||||
import io.realm.log.RealmLog
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
|
||||
/**
|
||||
* Rule for Realm DB tests.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class RealmDBTestRule(
|
||||
val baseModule: Any? = null,
|
||||
vararg val additionalModules: Array<Any>,
|
||||
) : AbstractAndroidTestRule() {
|
||||
|
||||
inline fun withDb(dbName: String, deleteDbAfterTest: Boolean = true, action: Realm.() -> Unit) {
|
||||
val name = dbName.replace(IRealmProvider.PATH_SEPARATOR, '-')
|
||||
val realm = IRealmProvider.instance().get("/indexing/java/$name") {
|
||||
baseModule?.let { baseModule ->
|
||||
modules(baseModule, *additionalModules)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
realm.action()
|
||||
} finally {
|
||||
if (deleteDbAfterTest) {
|
||||
realm.configuration.realmDirectory.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun apply(base: Statement?, description: Description?): Statement {
|
||||
Realm.init(context.applicationContext)
|
||||
RealmLog.setLevel(LogLevel.ALL)
|
||||
base?.evaluate()
|
||||
return NoOpStatement()
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.app.UiAutomation
|
||||
import android.content.Context
|
||||
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.UiDeviceAccessor
|
||||
import androidx.test.uiautomator.Until
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
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<Context>()
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UiDeviceAccessor.getUiAutomation
|
||||
*/
|
||||
val UiDevice.uiAutomation: UiAutomation
|
||||
get() = UiDeviceAccessor.getUiAutomation(this)
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.view.KeyCharacterMap
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiObject2
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.buildinfo.BuildInfo
|
||||
|
||||
private val keyCharMap by lazy {
|
||||
KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD)
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) =
|
||||
hasObjectWithText(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? =
|
||||
findObjectWithText(stringRes(text))
|
||||
|
||||
|
||||
fun UiDevice.getObjectWithText(text: String): UiObject2 {
|
||||
val obj = findObjectWithText(text)
|
||||
assertThat(obj).isNotNull()
|
||||
return obj!!
|
||||
}
|
||||
|
||||
fun UiDevice.getObjectWithText(@StringRes text: Int): UiObject2 = getObjectWithText(stringRes(text))
|
||||
|
||||
/**
|
||||
* Sends key events to this [UiDevice] to type the given [text].
|
||||
*/
|
||||
fun UiDevice.sendKeyEvents(text: String) {
|
||||
waitForIdle()
|
||||
val keyEvents = keyCharMap.getEvents(text.toCharArray())
|
||||
for (keyEvent in keyEvents) {
|
||||
uiAutomation.injectInputEvent(keyEvent, true)
|
||||
waitForIdle()
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UiDevice.hasObject
|
||||
*/
|
||||
fun UiObject2.hasObjectWithText(text: String) =
|
||||
hasObject(By.text(text))
|
||||
|
||||
/**
|
||||
* @see UiObject2.hasObject
|
||||
*/
|
||||
fun UiObject2.hasObjectWithText(@StringRes text: Int) =
|
||||
hasObjectWithText(stringRes(text))
|
||||
|
||||
/**
|
||||
* @see UiObject2.findObject
|
||||
*/
|
||||
fun UiObject2.findObjectWithText(text: String): UiObject2? =
|
||||
findObject(By.text(text))
|
||||
|
||||
/**
|
||||
* @see UiObject2.findObjectWithText
|
||||
*/
|
||||
fun UiObject2.findObjectWithText(@StringRes text: Int): UiObject2? =
|
||||
findObjectWithText(stringRes(text))
|
||||
|
||||
|
||||
fun UiObject2.getObjectWithText(text: String): UiObject2 {
|
||||
val obj = findObjectWithText(text)
|
||||
assertThat(obj).isNotNull()
|
||||
return obj!!
|
||||
}
|
||||
|
||||
fun UiObject2.getObjectWithText(@StringRes text: Int): UiObject2 = getObjectWithText(stringRes(text))
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import org.junit.runners.model.Statement
|
||||
|
||||
/**
|
||||
* A no-op statement.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class NoOpStatement : Statement() {
|
||||
override fun evaluate() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user