From a6f632b4ad069c98d70abe4c4b6eb732c5eec7d0 Mon Sep 17 00:00:00 2001 From: Birk Skyum <74932975+birkskyum@users.noreply.github.com> Date: Wed, 11 Jan 2023 17:05:16 +0100 Subject: [PATCH] Port FeatureOverview to Kotlin and remove empty activity tests (#686) --- .github/workflows/android-ci-pull.yml | 3 - .github/workflows/android-ci.yml | 3 - platform/android/Makefile | 5 - .../testapp/activity/activity.junit.ejs | 26 --- .../activity/FeatureOverviewActivity.java | 171 ------------------ .../activity/FeatureOverviewActivity.kt | 170 +++++++++++++++++ .../mapboxsdk/testapp/utils/NavUtils.java | 16 -- .../mapboxsdk/testapp/utils/NavUtils.kt | 12 ++ .../android/scripts/exclude-activity-gen.json | 53 ------ .../android/scripts/generate-test-code.js | 73 -------- 10 files changed, 182 insertions(+), 350 deletions(-) delete mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/activity.junit.ejs delete mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.kt delete mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/NavUtils.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/NavUtils.kt delete mode 100644 platform/android/scripts/exclude-activity-gen.json delete mode 100644 platform/android/scripts/generate-test-code.js diff --git a/.github/workflows/android-ci-pull.yml b/.github/workflows/android-ci-pull.yml index 601291e54..d32ec7d00 100644 --- a/.github/workflows/android-ci-pull.yml +++ b/.github/workflows/android-ci-pull.yml @@ -119,9 +119,6 @@ jobs: cmake --version make android-lib-arm-v8 - - name: Generate Espresso sanity tests - run: make test-code-android - - name: Store debug artifacts uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 293ffc130..87a70c6a6 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -110,9 +110,6 @@ jobs: - name: Build libmapbox-gl.so for arm-v8 run: make android-lib-arm-v8 - - name: Generate Espresso sanity tests - run: make test-code-android - - name: Build Test APK run: | MAPLIBRE_DEVELOPER_CONFIG_XML='${{ secrets.MAPLIBRE_DEVELOPER_CONFIG_XML }}' diff --git a/platform/android/Makefile b/platform/android/Makefile index ad58a70e2..9643de018 100644 --- a/platform/android/Makefile +++ b/platform/android/Makefile @@ -259,11 +259,6 @@ run-android-publish: gradle/configuration.gradle android-gfxinfo: adb shell dumpsys gfxinfo com.mapbox.mapboxsdk.testapp reset -# Generates Activity sanity tests -.PHONY: test-code-android -test-code-android: - node scripts/generate-test-code.js - # Runs checkstyle and lint on the java code .PHONY: android-check android-check : android-ktlint android-checkstyle android-lint-sdk android-lint-test-app run-android-nitpick diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/activity.junit.ejs b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/activity.junit.ejs deleted file mode 100644 index e7da16cbf..000000000 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/activity.junit.ejs +++ /dev/null @@ -1,26 +0,0 @@ -<% - const activity = locals[0]; - const subPackage = locals[1]; --%> -// This file is generated. Edit android/platform/scripts/generate-test-code.js, then run `make generate-test-android`. -package com.mapbox.mapboxsdk.testapp.activity.gen.<%- subPackage %>; - -import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner -import com.mapbox.mapboxsdk.testapp.activity.BaseTest -import com.mapbox.mapboxsdk.testapp.activity.<%- subPackage %>.<%- activity %> -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Sanity test for <%- activity %> - */ -@RunWith(AndroidJUnit4ClassRunner::class) -class <%- activity %>Test : BaseTest() { - @Test - fun testSanity() { - } - - override fun getActivityClass(): Class<*> { - return <%- activity %>::class.java - } -} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.java deleted file mode 100644 index 0cfc276f1..000000000 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.mapbox.mapboxsdk.testapp.activity; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.AsyncTask; -import android.os.Bundle; - -import androidx.annotation.StringRes; -import androidx.appcompat.app.AppCompatActivity; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.mapbox.mapboxsdk.testapp.R; -import com.mapbox.mapboxsdk.testapp.adapter.FeatureAdapter; -import com.mapbox.mapboxsdk.testapp.adapter.FeatureSectionAdapter; -import com.mapbox.mapboxsdk.testapp.model.activity.Feature; -import com.mapbox.mapboxsdk.testapp.utils.ItemClickSupport; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import timber.log.Timber; - -/** - * Activity shown when application is started - *

- * This activity will generate data for RecyclerView based on the AndroidManifest entries. - * It uses tags as category and description to order the different entries. - *

- */ -public class FeatureOverviewActivity extends AppCompatActivity { - - private static final String KEY_STATE_FEATURES = "featureList"; - - private RecyclerView recyclerView; - private FeatureSectionAdapter sectionAdapter; - private List features; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_feature_overview); - - recyclerView = (RecyclerView) findViewById(R.id.recyclerView); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener()); - recyclerView.setHasFixedSize(true); - - ItemClickSupport.addTo(recyclerView).setOnItemClickListener((recyclerView, position, view) -> { - if (!sectionAdapter.isSectionHeaderPosition(position)) { - int itemPosition = sectionAdapter.getConvertedPosition(position); - Feature feature = features.get(itemPosition); - startFeature(feature); - } - }); - - if (savedInstanceState == null) { - loadFeatures(); - } else { - features = savedInstanceState.getParcelableArrayList(KEY_STATE_FEATURES); - onFeaturesLoaded(features); - } - } - - private void loadFeatures() { - try { - new LoadFeatureTask().execute( - getPackageManager().getPackageInfo(getPackageName(), - PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA)); - } catch (PackageManager.NameNotFoundException exception) { - Timber.e(exception, "Could not resolve package info"); - } - } - - private void onFeaturesLoaded(List featuresList) { - features = featuresList; - if (featuresList == null || featuresList.isEmpty()) { - return; - } - - List sections = new ArrayList<>(); - String currentCat = ""; - for (int i = 0; i < features.size(); i++) { - String category = features.get(i).getCategory(); - if (!currentCat.equals(category)) { - sections.add(new FeatureSectionAdapter.Section(i, category)); - currentCat = category; - } - } - - FeatureSectionAdapter.Section[] dummy = new FeatureSectionAdapter.Section[sections.size()]; - sectionAdapter = new FeatureSectionAdapter( - this, R.layout.section_main_layout, R.id.section_text, new FeatureAdapter(features)); - sectionAdapter.setSections(sections.toArray(dummy)); - recyclerView.setAdapter(sectionAdapter); - } - - private void startFeature(Feature feature) { - Intent intent = new Intent(); - intent.setComponent(new ComponentName(getPackageName(), feature.getName())); - startActivity(intent); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelableArrayList(KEY_STATE_FEATURES, (ArrayList) features); - } - - private class LoadFeatureTask extends AsyncTask> { - - @Override - protected List doInBackground(PackageInfo... params) { - List features = new ArrayList<>(); - PackageInfo app = params[0]; - - String packageName = getApplicationContext().getPackageName(); - String metaDataKey = getString(R.string.category); - for (ActivityInfo info : app.activities) { - if (info.labelRes != 0 && info.name.startsWith(packageName) - && !info.name.equals(FeatureOverviewActivity.class.getName())) { - String label = getString(info.labelRes); - String description = resolveString(info.descriptionRes); - String category = resolveMetaData(info.metaData, metaDataKey); - features.add(new Feature(info.name, label, description, category)); - } - } - - if (!features.isEmpty()) { - Comparator comparator = (lhs, rhs) -> { - int result = lhs.getCategory().compareToIgnoreCase(rhs.getCategory()); - if (result == 0) { - result = lhs.getLabel().compareToIgnoreCase(rhs.getLabel()); - } - return result; - }; - Collections.sort(features, comparator); - } - - return features; - } - - private String resolveMetaData(Bundle bundle, String key) { - String category = null; - if (bundle != null) { - category = bundle.getString(key); - } - return category; - } - - private String resolveString(@StringRes int stringRes) { - try { - return getString(stringRes); - } catch (Resources.NotFoundException exception) { - return "-"; - } - } - - @Override - protected void onPostExecute(List features) { - super.onPostExecute(features); - onFeaturesLoaded(features); - } - } -} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.kt new file mode 100644 index 000000000..792bac7a6 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/FeatureOverviewActivity.kt @@ -0,0 +1,170 @@ +package com.mapbox.mapboxsdk.testapp.activity + +import android.content.ComponentName +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.res.Resources.NotFoundException +import android.os.AsyncTask +import android.os.Build +import android.os.Bundle +import android.view.View +import androidx.annotation.RequiresApi +import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener +import com.mapbox.mapboxsdk.testapp.R +import com.mapbox.mapboxsdk.testapp.adapter.FeatureAdapter +import com.mapbox.mapboxsdk.testapp.adapter.FeatureSectionAdapter +import com.mapbox.mapboxsdk.testapp.model.activity.Feature +import com.mapbox.mapboxsdk.testapp.utils.ItemClickSupport +import timber.log.Timber +import java.util.* + +/** + * Activity shown when application is started + * + * + * This activity will generate data for RecyclerView based on the AndroidManifest entries. + * It uses tags as category and description to order the different entries. + * + */ +class FeatureOverviewActivity : AppCompatActivity() { + private var recyclerView: RecyclerView? = null + private var sectionAdapter: FeatureSectionAdapter? = null + private var features: List? = null + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_feature_overview) + recyclerView = findViewById(R.id.recyclerView) as RecyclerView + recyclerView!!.layoutManager = LinearLayoutManager(this) + recyclerView!!.addOnItemTouchListener(SimpleOnItemTouchListener()) + recyclerView!!.setHasFixedSize(true) + ItemClickSupport.addTo(recyclerView) + .setOnItemClickListener { recyclerView: RecyclerView?, position: Int, view: View? -> + if (!sectionAdapter!!.isSectionHeaderPosition(position)) { + val itemPosition = sectionAdapter!!.getConvertedPosition(position) + val feature = features!![itemPosition] + startFeature(feature) + } + } + if (savedInstanceState == null) { + loadFeatures() + } else { + features = savedInstanceState.getParcelableArrayList(KEY_STATE_FEATURES) + onFeaturesLoaded(features) + } + } + + private fun loadFeatures() { + try { + LoadFeatureTask().execute( + packageManager.getPackageInfo( + packageName, + PackageManager.GET_ACTIVITIES or PackageManager.GET_META_DATA + ) + ) + } catch (exception: PackageManager.NameNotFoundException) { + Timber.e(exception, "Could not resolve package info") + } + } + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + private fun onFeaturesLoaded(featuresList: List?) { + features = featuresList + if (featuresList == null || featuresList.isEmpty()) { + return + } + val sections: MutableList = ArrayList() + var currentCat = "" + for (i in features!!.indices) { + val category = features!![i].category + if (currentCat != category) { + sections.add(FeatureSectionAdapter.Section(i, category)) + currentCat = category + } + } + + sectionAdapter = FeatureSectionAdapter( + this, + R.layout.section_main_layout, + R.id.section_text, + FeatureAdapter(features!!) + ) + sectionAdapter!!.setSections(sections.toTypedArray()) + recyclerView!!.adapter = sectionAdapter + } + + private fun startFeature(feature: Feature) { + val intent = Intent() + intent.component = ComponentName(packageName, feature.name) + startActivity(intent) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelableArrayList(KEY_STATE_FEATURES, features as ArrayList?) + } + + private inner class LoadFeatureTask : AsyncTask>() { + protected override fun doInBackground(vararg p0: PackageInfo?): List? { + val features: MutableList = ArrayList() + val app = p0[0] + val packageName = applicationContext.packageName + val metaDataKey = getString(R.string.category) + if (app != null) { + for (info in app.activities) { + if (info.labelRes != 0 && info.name.startsWith(packageName) && + info.name != FeatureOverviewActivity::class.java.name + ) { + val label = getString(info.labelRes) + val description = resolveString(info.descriptionRes) + val category = resolveMetaData(info.metaData, metaDataKey) + features.add(Feature(info.name, label, description, category!!)) + } + } + } + if (!features.isEmpty()) { + val comparator = Comparator { lhs: Feature, rhs: Feature -> + var result = lhs.category.compareTo(rhs.category, ignoreCase = true) + if (result == 0) { + result = lhs.getLabel().compareTo(rhs.getLabel(), ignoreCase = true) + } + result + } + Collections.sort(features, comparator) + } + return features + } + + private fun resolveMetaData(bundle: Bundle?, key: String): String? { + var category: String? = null + if (bundle != null) { + category = bundle.getString(key) + } + return category + } + + private fun resolveString(@StringRes stringRes: Int): String { + return try { + getString(stringRes) + } catch (exception: NotFoundException) { + "-" + } + } + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun onPostExecute(features: List) { + super.onPostExecute(features) + onFeaturesLoaded(features) + } + } + + companion object { + private const val KEY_STATE_FEATURES = "featureList" + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/NavUtils.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/NavUtils.java deleted file mode 100644 index 20c1b483a..000000000 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/NavUtils.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.mapbox.mapboxsdk.testapp.utils; - -import android.app.Activity; -import android.content.Intent; - -import androidx.annotation.NonNull; - -import com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity; - -public class NavUtils { - - public static void navigateHome(@NonNull Activity context) { - context.startActivity(new Intent(context, FeatureOverviewActivity.class)); - context.finish(); - } -} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/NavUtils.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/NavUtils.kt new file mode 100644 index 000000000..80fd14b38 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/utils/NavUtils.kt @@ -0,0 +1,12 @@ +package com.mapbox.mapboxsdk.testapp.utils + +import android.app.Activity +import android.content.Intent +import com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity + +object NavUtils { + fun navigateHome(context: Activity) { + context.startActivity(Intent(context, FeatureOverviewActivity::class.java)) + context.finish() + } +} diff --git a/platform/android/scripts/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json deleted file mode 100644 index ab84957e3..000000000 --- a/platform/android/scripts/exclude-activity-gen.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - "BaseLocationActivity", - "MapSnapshotterMarkerActivity", - "MapSnapshotterReuseActivity", - "MapSnapshotterLocalStyleActivity", - "LatLngBoundsActivity", - "BottomSheetActivity", - "MockLocationEngine", - "DeleteRegionActivity", - "RealTimeGeoJsonActivity", - "UpdateMetadataActivity", - "CarDrivingActivity", - "MyLocationTrackingModeActivity", - "MyLocationToggleActivity", - "MyLocationTintActivity", - "MyLocationDrawableActivity", - "DoubleMapActivity", - "LocationPickerActivity", - "GeoJsonClusteringActivity", - "RuntimeStyleTestActivity", - "AnimatedSymbolLayerActivity", - "ViewPagerActivity", - "MapFragmentActivity", - "SupportMapFragmentActivity", - "SnapshotActivity", - "NavigationDrawerActivity", - "QueryRenderedFeaturesBoxHighlightActivity", - "MultiMapActivity", - "MapInDialogActivity", - "ManualZoomActivity", - "MaxMinZoomActivity", - "ScrollByActivity", - "ZoomFunctionSymbolLayerActivity", - "SymbolGeneratorActivity", - "TextureViewTransparentBackgroundActivity", - "SimpleMapActivity", - "RenderTestActivity", - "SymbolLayerActivity", - "LocationFragmentActivity", - "MergeOfflineRegionsActivity", - "NestedViewPagerActivity", - "DebugModeActivity", - "TextureViewDebugModeActivity", - "RuntimeStyleActivity", - "GridSourceActivity", - "AnimatedImageSourceActivity", - "EspressoTestActivity", - "ChangeResourcesCachePathActivity", - "EspressoTestActivity", - "FragmentBackStackActivity", - "ChildFragmentMapInDialogActivity", - "PerformanceMeasurementActivity" -] diff --git a/platform/android/scripts/generate-test-code.js b/platform/android/scripts/generate-test-code.js deleted file mode 100644 index 17d79bf19..000000000 --- a/platform/android/scripts/generate-test-code.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const ejs = require('ejs'); - -global.iff = function (condition, val) { - return condition() ? val : ""; -} - -global.camelize = function (str) { - return str.replace(/(?:^|-)(.)/g, function (_, x) { - return x.toUpperCase(); - }); -} - -const excludeClasses = JSON.parse(fs.readFileSync('scripts/exclude-activity-gen.json', 'utf8')); -const appBasePath = 'MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity'; -const testBasePath = 'MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/gen'; -const subPackages = fs.readdirSync(appBasePath); -const ejsConversionTask = ejs.compile(fs.readFileSync('MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/activity.junit.ejs', 'utf8'), {strict: true}); - -if (!fs.existsSync(testBasePath)){ - fs.mkdirSync(testBasePath); -} - -console.log("\nGenerating test activities:\n"); -var generatedClasses = []; -var excludedClasses = []; -for(const subPackage of subPackages) { - if(!(subPackage.slice(-5) === '.java')) { - const activities = fs.readdirSync(`${appBasePath}/${subPackage}`); - - // create directories for package - if (!fs.existsSync(`${testBasePath}/${subPackage}`)){ - fs.mkdirSync(`${testBasePath}/${subPackage}`); - } - - for (const activity of activities) { - var activityName; - if (activity.slice(-5) === '.java') { - // .java file - activityName = activity.slice(0, -5); - } else { - // .java file - activityName = activity.slice(0, -3); - } - - // create path for test file - const filePath = `${testBasePath}/${subPackage}/${activityName}Test.kt`; - - // try removing previous generated files - try { - fs.accessSync(filePath, fs.F_OK); - fs.unlinkSync(filePath); - } catch (e) { - } - - // only generate test file if not part of exclude list + if contains Activity in name - if ((!(excludeClasses.indexOf(activityName) > -1)) && activityName.includes("Activity")) { - fs.writeFileSync(filePath, ejsConversionTask([activityName, subPackage])); - generatedClasses.push(activityName); - }else{ - excludedClasses.push(activityName); - } - } - } -} - -for(const generatedClass of generatedClasses){ - console.log(`${generatedClass}Test`); -} - -console.log(`\nFinished generating ${generatedClasses.length} activity sanity tests, excluded ${excludeClasses.length} classes.\n`); \ No newline at end of file