-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7eac46b
Showing
102 changed files
with
2,417 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
.idea | ||
*.iml | ||
build | ||
target | ||
|
||
.gradle | ||
local.properties | ||
*.lock | ||
/app/snippets-api | ||
/backend/out | ||
NoBullshit/app/google-services.json | ||
|
||
.idea/modules/nobullshit.iml | ||
|
||
.idea/vcs.xml | ||
|
||
android/app/google-services.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# No Bullshit - Perfect jobs only | ||
|
||
No Bullshit is an open source project that wants to help developers **find the perfect job**. An expert developer reviews each job submitted through the platform. We publish only ***the best***... 😎 | ||
|
||
This project is intented to show how to create a **full project** using Kotlin. Therefore, you'll find two main modules : | ||
|
||
- **backend** : Contains the [Ktor](https://ktor.io/) backend configured to run on [Google App Engine](https://cloud.google.com/appengine/?hl=fr). It also uses [Freemarker](https://freemarker.apache.org/) for Java/html template. Data are persisted in [Firestore](https://cloud.google.com/firestore/). | ||
- **android** : Contains the Android app written with Kotlin. | ||
|
||
# Demo | ||
Because a picture is worth a thousand words : | ||
- 🌏The backend : [https://www.nobullshit.io/](https://www.nobullshit.io/) | ||
- 📱The mobile app : # | ||
|
||
# Where to start ? | ||
You want to contribute or understand what this is all about, but you don't know where to start? Here are some useful resources : | ||
|
||
**About the backend** : | ||
- [Ktor Quickstart](https://ktor.io/quickstart/index.html) | ||
- [Ktor on Google Cloud Appengine Standard](https://cloud.google.com/community/tutorials/kotlin-ktor-app-engine-java8) | ||
- [Ktor Samples](https://github.com/ktorio/ktor-samples) ❤️ | ||
- [Firestore](https://cloud.google.com/firestore/docs/) | ||
|
||
# Running samples | ||
The backend sample can be run **locally** ([http://localhost:8080/](http://localhost:8080/)) using the following script : | ||
|
||
./run-locally.sh | ||
If you want to deploy it to **your own GAE project**, you can use the following script : | ||
|
||
|
||
./deploy.sh YOUR_GAE_PROJECT_ID | ||
# Running tests | ||
If you want to run the tests : | ||
|
||
./gradlew test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
*.iml | ||
.gradle | ||
/local.properties | ||
/.idea/caches/build_file_checksums.ser | ||
/.idea/libraries | ||
/.idea/modules.xml | ||
/.idea/workspace.xml | ||
.DS_Store | ||
/build | ||
/captures | ||
.externalNativeBuild |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
apply plugin: 'com.android.application' | ||
apply plugin: 'kotlin-android' | ||
apply plugin: 'kotlin-android-extensions' | ||
apply plugin: 'kotlin-kapt' | ||
apply plugin: "kotlin-allopen" | ||
|
||
android { | ||
compileSdkVersion 28 | ||
defaultConfig { | ||
applicationId "io.nobullshit.nobullshit" | ||
minSdkVersion 21 | ||
targetSdkVersion 28 | ||
versionCode 1 | ||
versionName "1.0" | ||
testInstrumentationRunner "io.nobullshit.nobullshit.TIRunner" | ||
} | ||
buildTypes { | ||
debug { } | ||
release { | ||
minifyEnabled false | ||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
} | ||
} | ||
sourceSets { | ||
androidTest.java.srcDirs += "src/test-common/java" | ||
test.java.srcDirs += "src/test-common/java" | ||
} | ||
} | ||
|
||
// allows mocking for classes w/o directly opening them for release builds | ||
allOpen { | ||
annotation("io.nobullshit.nobullshit.testing.OpenForTesting") | ||
} | ||
|
||
dependencies { | ||
implementation fileTree(dir: 'libs', include: ['*.jar']) | ||
// KOTLIN | ||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||
// ANDROID | ||
implementation "androidx.appcompat:appcompat:$android_appcompat_version" | ||
implementation "androidx.constraintlayout:constraintlayout:$android_constraint_layout_version" | ||
implementation "com.google.android.material:material:$android_material_version" | ||
implementation "androidx.paging:paging-runtime-ktx:$android_pagging_version" | ||
// FIREBASE | ||
implementation "com.google.firebase:firebase-core:$firebase_version" | ||
implementation "com.firebaseui:firebase-ui-firestore:$firestore_ui_version" | ||
// GLIDE | ||
implementation "com.github.bumptech.glide:glide:$glide_version" | ||
kapt "com.github.bumptech.glide:compiler:$glide_version" | ||
// DAGGER 2 | ||
implementation "com.google.dagger:dagger:$dagger_version" | ||
implementation "com.google.dagger:dagger-android:$dagger_version" | ||
implementation "com.google.dagger:dagger-android-support:$dagger_version" | ||
kapt "com.google.dagger:dagger-compiler:$dagger_version" | ||
kapt "com.google.dagger:dagger-android-processor:$dagger_version" | ||
compileOnly 'javax.annotation:jsr250-api:1.0' | ||
// CUSTOM TABS | ||
implementation "androidx.browser:browser:$custom_tab_version" | ||
// UNIT TEST | ||
implementation 'androidx.legacy:legacy-support-v4:1.0.0' | ||
testImplementation 'junit:junit:4.12' | ||
// INSTRUMENTED TEST | ||
androidTestImplementation 'androidx.test.ext:junit:1.1.0' | ||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' | ||
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1' | ||
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.1' | ||
androidTestImplementation "io.mockk:mockk-android:1.8.13" | ||
kaptAndroidTest 'com.google.dagger:dagger:2.17' | ||
} | ||
apply plugin: 'com.google.gms.google-services' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 changes: 15 additions & 0 deletions
15
android/app/src/androidTest/java/io/nobullshit/nobullshit/TIRunner.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package io.nobullshit.nobullshit | ||
|
||
import android.app.Application | ||
import android.content.Context | ||
import androidx.test.runner.AndroidJUnitRunner | ||
import io.nobullshit.nobullshit.base.TIBaseApplication | ||
|
||
/** | ||
* Custom runner to disable dependency injection. | ||
*/ | ||
class TIRunner : AndroidJUnitRunner() { | ||
override fun newApplication(cl: ClassLoader, className: String, context: Context): Application { | ||
return super.newApplication(cl, TIBaseApplication::class.java.name, context) | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
android/app/src/androidTest/java/io/nobullshit/nobullshit/base/TIBaseApplication.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package io.nobullshit.nobullshit.base | ||
|
||
import android.app.Application | ||
|
||
/** | ||
* We use a separate App for tests to prevent initializing dependency injection. | ||
* | ||
* See [io.nobullshit.nobullshit.TIRunner]. | ||
*/ | ||
class TIBaseApplication : Application() |
105 changes: 105 additions & 0 deletions
105
android/app/src/androidTest/java/io/nobullshit/nobullshit/ui/joblist/TIJobList.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package io.nobullshit.nobullshit.ui.joblist | ||
|
||
import android.content.Intent | ||
import androidx.browser.customtabs.CustomTabsIntent | ||
import androidx.recyclerview.widget.RecyclerView | ||
import androidx.test.espresso.Espresso.onView | ||
import androidx.test.espresso.action.ViewActions.click | ||
import androidx.test.espresso.assertion.ViewAssertions.matches | ||
import androidx.test.espresso.contrib.RecyclerViewActions | ||
import androidx.test.espresso.intent.Intents.intended | ||
import androidx.test.espresso.intent.Intents.intending | ||
import androidx.test.espresso.intent.matcher.IntentMatchers.* | ||
import androidx.test.espresso.intent.rule.IntentsTestRule | ||
import androidx.test.espresso.matcher.ViewMatchers | ||
import androidx.test.espresso.matcher.ViewMatchers.* | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import androidx.test.filters.LargeTest | ||
import androidx.test.rule.ActivityTestRule | ||
import com.google.firebase.firestore.* | ||
import io.mockk.* | ||
import io.nobullshit.nobullshit.Datasets | ||
import io.nobullshit.nobullshit.Datasets.SINGLE_JOB | ||
import io.nobullshit.nobullshit.R | ||
import io.nobullshit.nobullshit.db.dao.JobDao | ||
import io.nobullshit.nobullshit.extension.getCategoryTitle | ||
import io.nobullshit.nobullshit.extension.getTypeTitle | ||
import io.nobullshit.nobullshit.model.Company | ||
import io.nobullshit.nobullshit.model.Job | ||
import io.nobullshit.nobullshit.testing.SingleFragmentActivity | ||
import io.nobullshit.nobullshit.util.* | ||
import org.hamcrest.CoreMatchers.allOf | ||
import org.hamcrest.CoreMatchers.not | ||
|
||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
|
||
import org.junit.Rule | ||
|
||
/** | ||
* Instrumented tests for [JobListFragment] | ||
*/ | ||
@RunWith(AndroidJUnit4::class) | ||
@LargeTest | ||
class TIJobList { | ||
|
||
@Rule | ||
@JvmField | ||
val activityRule = IntentsTestRule(SingleFragmentActivity::class.java, true, true) | ||
|
||
@Test | ||
fun testLoadingSingleJob() { | ||
this.configureFragmentBeforeTest(mockQuery(Job::class.java, Datasets.SINGLE_JOB)) | ||
|
||
onView(withId(R.id.fragment_job_list_refresh)).check(matches(not(isRefreshing()))) | ||
onView(withId(R.id.fragment_job_list_rv)).check(matches((hasItemCount(1)))) | ||
onView(allOf(withId(R.id.item_job_title), withText(Datasets.SINGLE_JOB.title))).check(matches(isDisplayed())) | ||
onView(allOf(withId(R.id.item_job_company_title), withText(Datasets.SINGLE_JOB.company.title))).check(matches(isDisplayed())) | ||
onView(withId(R.id.item_job_chip_group)).check(matches(hasChildCount(2))) | ||
onView(withId(R.id.item_job_chip_group)).check(matches(atPositionChipGroup(0, withText(Datasets.SINGLE_JOB.getCategoryTitle(activityRule.activity))))) | ||
onView(withId(R.id.item_job_chip_group)).check(matches(atPositionChipGroup(1, withText(Datasets.SINGLE_JOB.getTypeTitle(activityRule.activity))))) | ||
} | ||
|
||
@Test | ||
fun testLoadingMultipleJob() { | ||
this.configureFragmentBeforeTest(mockQuery(Job::class.java, *Datasets.MULTIPLE_JOBS)) | ||
|
||
onView(withId(R.id.fragment_job_list_refresh)).check(matches(not(isRefreshing()))) | ||
onView(withId(R.id.fragment_job_list_rv)).check(matches((hasItemCount(30)))) | ||
} | ||
|
||
@Test | ||
fun testLoadingNothing() { | ||
this.configureFragmentBeforeTest(mockQuery(Job::class.java)) | ||
|
||
onView(withId(R.id.fragment_job_list_refresh)).check(matches(not(isRefreshing()))) | ||
onView(withId(R.id.fragment_job_list_rv)).check(matches((hasItemCount(0)))) | ||
} | ||
|
||
@Test | ||
fun testClickOnJob() { | ||
this.configureFragmentBeforeTest(mockQuery(Job::class.java, Datasets.SINGLE_JOB)) | ||
|
||
onView(withId(R.id.fragment_job_list_rv)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, click())) | ||
intended(hasAction(Intent.ACTION_VIEW)) | ||
intended(hasData(Datasets.SINGLE_JOB.url)) | ||
} | ||
|
||
// --- | ||
|
||
/** | ||
* Create a new [JobListFragment] | ||
* and mock the [JobDao] with custom response [Query]. | ||
* The fragment will be set inside a fake activity [SingleFragmentActivity] | ||
*/ | ||
private fun configureFragmentBeforeTest(query: Query) { | ||
val fragment = JobListFragment().apply { | ||
val firestore = mockk<FirebaseFirestore>() | ||
jobDao = object : JobDao(firestore) { | ||
override fun listApprovedJobs() = query | ||
} | ||
} | ||
activityRule.activity.setFragment(fragment) | ||
} | ||
} | ||
|
51 changes: 51 additions & 0 deletions
51
android/app/src/androidTest/java/io/nobullshit/nobullshit/util/MockUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package io.nobullshit.nobullshit.util | ||
|
||
import com.google.android.gms.tasks.TaskExecutors | ||
import com.google.android.gms.tasks.Tasks | ||
import com.google.firebase.firestore.* | ||
import io.mockk.every | ||
import io.mockk.mockk | ||
import java.util.concurrent.Callable | ||
import java.util.concurrent.Executor | ||
|
||
/** | ||
* Mock a [FirebaseFirestore] [Query] | ||
* with custom generic object(s) [models] | ||
*/ | ||
fun <T> mockQuery(valueType: Class<T>, vararg models: T): Query { | ||
|
||
// First query that getting mocked data | ||
val mockQuery = mockk<Query>() | ||
val mockSnapshot = mockk<QuerySnapshot>() | ||
val mockDocuments = mutableListOf<QueryDocumentSnapshot>() | ||
|
||
/** | ||
* Create a mock of each [DocumentSnapshot] | ||
* and fill it with an object [T] from [models] | ||
*/ | ||
models.forEach { | ||
val mockDocument = mockk<QueryDocumentSnapshot>(relaxed = true) | ||
every { mockDocument.toObject(valueType) } returns it | ||
every { mockQuery.startAfter(mockDocument) } returns mockQuery | ||
mockDocuments.add(mockDocument) | ||
} | ||
every { mockSnapshot.documents } returns mockDocuments.toList() | ||
|
||
// Second query that must return EMPTY data to cancel future loads | ||
val mockQueryEmpty = mockk<Query>(relaxed = true) | ||
val mockSnapshotEmpty = mockk<QuerySnapshot>() | ||
every { mockSnapshotEmpty.documents } returns listOf<DocumentSnapshot>() | ||
|
||
/** | ||
* Mocking two requests [Tasks]. | ||
* The first one will get mocked data. | ||
* The second will return empty list of [DocumentSnapshot]. | ||
* Both tasks use [TaskExecutors.MAIN_THREAD] executor. | ||
*/ | ||
every { mockQuery.get(Source.DEFAULT) } returns Tasks.call { mockSnapshot } | ||
every { mockQuery.limit(60) } returns mockQuery | ||
every { mockQuery.limit(20) } returns mockQueryEmpty | ||
every { mockQueryEmpty.get(Source.DEFAULT) } returns Tasks.call { mockSnapshotEmpty } | ||
|
||
return mockQuery | ||
} |
Oops, something went wrong.