Sample app, all samples test
This commit is contained in:
		
							parent
							
								
									c96ea690f6
								
							
						
					
					
						commit
						df23339dba
					
				| @ -1,5 +1,33 @@ | ||||
| # Markwon sample app | ||||
| 
 | ||||
| ## Distribution | ||||
| 
 | ||||
| Sample app is distributed via special parent-less branch [sample-store](https://github.com/noties/Markwon/tree/sample-store). | ||||
| Inside the app, under version badges, tap `CHECK FOR UPDATES` to check for updates. Sample app | ||||
| is not attached to main libraries versions and can be _released_ independently. | ||||
| 
 | ||||
| Application is signed with `keystore.jks`, which fingerprints are: | ||||
| * __SHA1__: `BA:70:A5:D2:40:65:F1:FA:88:90:59:BA:FC:B7:31:81:E6:37:D9:41` | ||||
| * __SHA256__: `82:C9:61:C5:DF:35:B1:CB:29:D5:48:83:FB:EB:9F:3E:7D:52:67:63:4F:D2:CE:0A:2D:70:17:85:FF:48:67:51` | ||||
| 
 | ||||
| [Download latest APK](https://github.com/noties/Markwon/raw/sample-store/markwon-debug.apk) | ||||
| 
 | ||||
| ## Deeplink | ||||
| 
 | ||||
| Sample app handles special `markwon` scheme: | ||||
| * `markwon://sample/{ID}` to open specific sample given the `{ID}` | ||||
| * `markwon://search?q={TEXT TO SEARCH}&a={ARTIFACT}&t={TAG}` | ||||
| 
 | ||||
| Please note that search deeplink can have one of type: artifact or tag (if both are specified artifact will be used). | ||||
| 
 | ||||
| To test locally: | ||||
| 
 | ||||
| ``` | ||||
| adb shell am start -a android.intent.action.ACTION_VIEW -d markwon://sample/ID | ||||
| ``` | ||||
| 
 | ||||
| Please note that you might need to _url encode_ the `-d` argument | ||||
| 
 | ||||
| ## Building | ||||
| 
 | ||||
| When adding/removing samples _most likely_ a clean build would be required. | ||||
| @ -7,6 +35,22 @@ First, for annotation processor to create `samples.json`. And secondly, | ||||
| in order for Android Gradle plugin to bundle resources references via | ||||
| symbolic links (the `sample.json` itself and `io.noties.markwon.app.samples.*` directory) | ||||
| 
 | ||||
| ```gradle | ||||
| ``` | ||||
| ./gradlew :app-s:clean :app-s:asDe | ||||
| ``` | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| ## Tests | ||||
| 
 | ||||
| This app uses [Robolectric](https://robolectric.org)(v3.8) for tests which is incompatible | ||||
| with JDK > 1.8. In order to run tests from command line with IDEA-bundled JDK - a special argument is | ||||
| required: | ||||
| 
 | ||||
| ``` | ||||
| ./gradlew :app-s:testDe -Dorg.gradle.java.home="{INSERT BUNDLED JDK PATH HERE}" | ||||
| ``` | ||||
| 
 | ||||
| To obtain bundled JDK: | ||||
| * open `Project Structure...` | ||||
| * open `SDK Location` | ||||
| * copy contents of the field under `JDK Location` | ||||
| @ -139,4 +139,10 @@ dependencies { | ||||
|         implementation it['android-svg'] | ||||
|         implementation it['android-gif-impl'] | ||||
|     } | ||||
| 
 | ||||
|     deps['test'].with { | ||||
|         testImplementation it['junit'] | ||||
|         testImplementation it['robolectric'] | ||||
|         testImplementation it['mockito'] | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -14,7 +14,8 @@ import java.util.concurrent.ExecutorService | ||||
| import java.util.concurrent.Executors | ||||
| 
 | ||||
| @Suppress("unused") | ||||
| class App : Application() { | ||||
| // `open` is required for tests (to create a spy mockito instance) | ||||
| open class App : Application() { | ||||
| 
 | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
|  | ||||
| @ -26,7 +26,9 @@ public class SimpleExtensionSample extends MarkwonTextViewSample { | ||||
|       "# SimpleExt\n" + | ||||
|       "\n" + | ||||
|       "+let's start with `+`, ??then we can use this, and finally @@this$$??+"; | ||||
|     ; | ||||
| 
 | ||||
|     // NB! we cannot have multiple delimiter processor with the same character | ||||
|     //  (even if lengths are different) | ||||
| 
 | ||||
|     final Markwon markwon = Markwon.builder(context) | ||||
|       .usePlugin(SimpleExtPlugin.create(plugin -> { | ||||
| @ -36,7 +38,7 @@ public class SimpleExtensionSample extends MarkwonTextViewSample { | ||||
|           .addExtension( | ||||
|             2, | ||||
|             '@', | ||||
|             '?', | ||||
|             '$', | ||||
|             (configuration, props) -> new ForegroundColorSpan(Color.RED) | ||||
|           ); | ||||
|       })) | ||||
|  | ||||
| @ -162,7 +162,7 @@ public class HtmlDetailsSample extends MarkwonSample { | ||||
|   private TextView appendTextView() { | ||||
|     final View view = LayoutInflater.from(context) | ||||
|       .inflate(R.layout.view_html_details_text_view, content, false); | ||||
|     final TextView textView = view.findViewById(R.id.text); | ||||
|     final TextView textView = view.findViewById(R.id.text_view); | ||||
|     content.addView(view); | ||||
|     return textView; | ||||
|   } | ||||
|  | ||||
| @ -19,19 +19,24 @@ public abstract class SampleUtils { | ||||
|     @NonNull | ||||
|     public static List<Sample> readSamples(@NonNull Context context) { | ||||
| 
 | ||||
|         final Gson gson = new Gson(); | ||||
| 
 | ||||
|         try (InputStream inputStream = context.getAssets().open("samples.json")) { | ||||
|             return gson.fromJson( | ||||
|                     new InputStreamReader(inputStream), | ||||
|                     new TypeToken<List<Sample>>() { | ||||
|                     }.getType() | ||||
|             ); | ||||
|             return readSamples(inputStream); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // NB! stream is not closed by this method | ||||
|     @NonNull | ||||
|     public static List<Sample> readSamples(@NonNull InputStream inputStream) { | ||||
|         final Gson gson = new Gson(); | ||||
|         return gson.fromJson( | ||||
|                 new InputStreamReader(inputStream), | ||||
|                 new TypeToken<List<Sample>>() { | ||||
|                 }.getType() | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private SampleUtils() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <TextView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:id="@+id/text" | ||||
|     android:id="@+id/text_view" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:padding="8dip" | ||||
|  | ||||
							
								
								
									
										125
									
								
								app-sample/src/test/java/io/noties/markwon/app/AllSamples.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								app-sample/src/test/java/io/noties/markwon/app/AllSamples.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | ||||
| package io.noties.markwon.app | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.os.Build | ||||
| import android.text.SpannableString | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.widget.Button | ||||
| import android.widget.EditText | ||||
| import android.widget.ScrollView | ||||
| import android.widget.TextView | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import io.noties.markwon.app.sample.Sample | ||||
| import io.noties.markwon.app.sample.ui.MarkwonSample | ||||
| import io.noties.markwon.app.utils.SampleUtils | ||||
| import org.junit.Test | ||||
| import org.junit.runner.RunWith | ||||
| import org.mockito.ArgumentMatchers.any | ||||
| import org.mockito.ArgumentMatchers.anyBoolean | ||||
| import org.mockito.ArgumentMatchers.anyInt | ||||
| import org.mockito.ArgumentMatchers.eq | ||||
| import org.mockito.Mockito.RETURNS_MOCKS | ||||
| import org.mockito.Mockito.`when` | ||||
| import org.mockito.Mockito.mock | ||||
| import org.mockito.Mockito.spy | ||||
| import org.robolectric.ParameterizedRobolectricTestRunner | ||||
| import org.robolectric.RuntimeEnvironment | ||||
| import org.robolectric.annotation.Config | ||||
| 
 | ||||
| @RunWith(org.robolectric.ParameterizedRobolectricTestRunner::class) | ||||
| @Config(manifest = "src/main/AndroidManifest.xml", sdk = [Build.VERSION_CODES.O]) | ||||
| class AllSamples(private val sample: Sample) { | ||||
| 
 | ||||
|     @Test | ||||
|     fun sample() { | ||||
|         val markwonSample: MarkwonSample = Class.forName(sample.javaClassName).newInstance() as MarkwonSample | ||||
| 
 | ||||
|         val inflater = mock(LayoutInflater::class.java).apply { | ||||
|             // mock must be initialized (_finished_) before we | ||||
|             //  can start `thenReturn` or creating another mock | ||||
|             val view = view | ||||
|             val inflater = this | ||||
|             // html-details require this, it is creating views manually | ||||
|             val context = spy(RuntimeEnvironment.application).apply { | ||||
|                 `when`(getSystemService(eq(Context.LAYOUT_INFLATER_SERVICE))) | ||||
|                         .thenReturn(inflater) | ||||
|             } | ||||
|             `when`(view.context).thenReturn(context) | ||||
|             `when`(this.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean())) | ||||
|                     .thenReturn(view) | ||||
|         } | ||||
| 
 | ||||
|         val view = markwonSample.createView( | ||||
|                 inflater, | ||||
|                 mock(ViewGroup::class.java)) | ||||
|         markwonSample.onViewCreated(view) | ||||
|     } | ||||
| 
 | ||||
|     private val view: View | ||||
|         get() { | ||||
|             val view: View = mock(View::class.java) | ||||
|             view.apply { | ||||
|                 // textView | ||||
|                 val textView = textView | ||||
|                 `when`(findViewById<TextView>(eq(R.id.text_view))) | ||||
|                         .thenReturn(textView) | ||||
| 
 | ||||
|                 `when`(findViewById<EditText>(eq(R.id.edit_text))) | ||||
|                         .thenReturn(mock(EditText::class.java)) | ||||
| 
 | ||||
|                 // scrollView | ||||
|                 `when`(findViewById<ScrollView>(eq(R.id.scroll_view))) | ||||
|                         .thenReturn(mock(ScrollView::class.java)) | ||||
| 
 | ||||
|                 // recyclerView | ||||
|                 `when`(findViewById<RecyclerView>(eq(R.id.recycler_view))) | ||||
|                         .thenReturn(mock(RecyclerView::class.java)) | ||||
| 
 | ||||
|                 // html-details ViewGroup | ||||
|                 `when`(findViewById<ViewGroup>(R.id.content)) | ||||
|                         .thenReturn(mock(ViewGroup::class.java)) | ||||
| 
 | ||||
|                 // special editor views | ||||
|                 arrayOf( | ||||
|                         R.id.bold, | ||||
|                         R.id.italic, | ||||
|                         R.id.strike, | ||||
|                         R.id.quote, | ||||
|                         R.id.code) | ||||
|                         .forEach { | ||||
|                             val button = mock(Button::class.java).apply { | ||||
|                                 `when`(text).thenReturn("") | ||||
|                             } | ||||
|                             `when`(findViewById<Button>(eq(it))) | ||||
|                                     .thenReturn(button) | ||||
|                         } | ||||
|             } | ||||
|             return view | ||||
|         } | ||||
| 
 | ||||
|     private val textView: TextView | ||||
|         get() { | ||||
|             val textView: TextView = mock(TextView::class.java, RETURNS_MOCKS) | ||||
|             textView.apply { | ||||
|                 `when`(text) | ||||
|                         .thenReturn(SpannableString("")) | ||||
|                 `when`(getTag(eq(R.id.markwon_drawables_scheduler_last_text_hashcode))) | ||||
|                         .thenReturn(0) | ||||
|             } | ||||
|             return textView | ||||
|         } | ||||
| 
 | ||||
|     companion object { | ||||
|         @JvmStatic | ||||
|         @ParameterizedRobolectricTestRunner.Parameters(name = "{index}: {0}") | ||||
|         public fun samples(): Collection<Any> { | ||||
|             return AllSamples::class.java.classLoader!!.getResourceAsStream("samples.json").use { inputStream -> | ||||
|                 SampleUtils | ||||
|                         .readSamples(inputStream) | ||||
|                         .map { arrayOf<Any>(it) } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1
									
								
								app-sample/src/test/resources/samples.json
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								app-sample/src/test/resources/samples.json
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| ../../../samples.json | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov