Sample app, all samples test
This commit is contained in:
parent
c96ea690f6
commit
df23339dba
@ -1,5 +1,33 @@
|
|||||||
# Markwon sample app
|
# 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
|
## Building
|
||||||
|
|
||||||
When adding/removing samples _most likely_ a clean build would be required.
|
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
|
in order for Android Gradle plugin to bundle resources references via
|
||||||
symbolic links (the `sample.json` itself and `io.noties.markwon.app.samples.*` directory)
|
symbolic links (the `sample.json` itself and `io.noties.markwon.app.samples.*` directory)
|
||||||
|
|
||||||
```gradle
|
```
|
||||||
./gradlew :app-s:clean :app-s:asDe
|
./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-svg']
|
||||||
implementation it['android-gif-impl']
|
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
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class App : Application() {
|
// `open` is required for tests (to create a spy mockito instance)
|
||||||
|
open class App : Application() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
@ -26,7 +26,9 @@ public class SimpleExtensionSample extends MarkwonTextViewSample {
|
|||||||
"# SimpleExt\n" +
|
"# SimpleExt\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"+let's start with `+`, ??then we can use this, and finally @@this$$??+";
|
"+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)
|
final Markwon markwon = Markwon.builder(context)
|
||||||
.usePlugin(SimpleExtPlugin.create(plugin -> {
|
.usePlugin(SimpleExtPlugin.create(plugin -> {
|
||||||
@ -36,7 +38,7 @@ public class SimpleExtensionSample extends MarkwonTextViewSample {
|
|||||||
.addExtension(
|
.addExtension(
|
||||||
2,
|
2,
|
||||||
'@',
|
'@',
|
||||||
'?',
|
'$',
|
||||||
(configuration, props) -> new ForegroundColorSpan(Color.RED)
|
(configuration, props) -> new ForegroundColorSpan(Color.RED)
|
||||||
);
|
);
|
||||||
}))
|
}))
|
||||||
|
@ -162,7 +162,7 @@ public class HtmlDetailsSample extends MarkwonSample {
|
|||||||
private TextView appendTextView() {
|
private TextView appendTextView() {
|
||||||
final View view = LayoutInflater.from(context)
|
final View view = LayoutInflater.from(context)
|
||||||
.inflate(R.layout.view_html_details_text_view, content, false);
|
.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);
|
content.addView(view);
|
||||||
return textView;
|
return textView;
|
||||||
}
|
}
|
||||||
|
@ -19,19 +19,24 @@ public abstract class SampleUtils {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public static List<Sample> readSamples(@NonNull Context context) {
|
public static List<Sample> readSamples(@NonNull Context context) {
|
||||||
|
|
||||||
final Gson gson = new Gson();
|
|
||||||
|
|
||||||
try (InputStream inputStream = context.getAssets().open("samples.json")) {
|
try (InputStream inputStream = context.getAssets().open("samples.json")) {
|
||||||
return gson.fromJson(
|
return readSamples(inputStream);
|
||||||
new InputStreamReader(inputStream),
|
|
||||||
new TypeToken<List<Sample>>() {
|
|
||||||
}.getType()
|
|
||||||
);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(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() {
|
private SampleUtils() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
<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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dip"
|
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