diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml
new file mode 100644
index 000000000..31be1465a
--- /dev/null
+++ b/.github/workflows/run-test.yml
@@ -0,0 +1,178 @@
+name: Tests
+
+on:
+ pull_request:
+ push:
+ branches:
+ - "develop"
+ - "main"
+ schedule:
+ # Run twice a day the sanity check, at 9:13 and 21:13.
+ # You ask why 13? because probably less people schedule their tasks at exactly this time, so I
+ # guess CI is less occupied. And 13 is a lucky number ;)
+ - cron: "13 9,21 * * *"
+
+concurrency:
+ group: ${{ github.ref }}-${{ github.workflow }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+ actions: read
+ checks: write
+
+jobs:
+ unit-tests:
+ name: Unit Tests
+ runs-on: ubuntu-20.04
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Prepare Android Environment
+ uses: ./.github/actions/prepare-android-env
+
+ - name: Run test
+ run: ./gradlew test
+
+ - name: Upload tests results
+ if: always()
+ uses: actions/upload-artifact@v3
+ with:
+ name: test-results
+ path: |
+ */build/test-results/**
+ */build/paparazzi/failures/**
+ retention-days: 5
+
+ android-tests:
+ name: Run UI tests on Firebase Test Lab
+ runs-on: ubuntu-20.04
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: LFS-warning - Prevent large files that are not LFS tracked
+ uses: ppremk/lfs-warning@v3.2
+
+ - name: Prepare Android Environment
+ uses: ./.github/actions/prepare-android-env
+
+ - name: Assemble App Debug APK and Android Instrumentation Tests
+ run: ./gradlew assembleDebug assembleDebugAndroidTest
+
+ - id: auth
+ name: Authenticate to Google Cloud
+ uses: google-github-actions/auth@v1
+ with:
+ credentials_json: ${{ secrets.SERVICE_ACCOUNT }}
+
+ - name: Set up Cloud SDK
+ uses: google-github-actions/setup-gcloud@v1
+ with:
+ install_components: "gsutil"
+
+ - name: Generate random directory
+ id: generate-dir
+ run: |-
+ echo "results_dir=$(date +%F_%T)-${RANDOM}" >> "$GITHUB_OUTPUT"
+ echo "bucket=test-lab-07qs3ns6c51bi-iazpthysivhkq" >> "$GITHUB_OUTPUT"
+
+ - name: Run tests on Firebase Test Lab
+ run: |-
+ gcloud firebase test android run ".github/tests.yml:android-pixel-2" --results-dir="${{ steps.generate-dir.outputs.results_dir }}" --results-bucket="${{ steps.generate-dir.outputs.bucket }}"
+
+ - name: Download test results from Firebase Test Lab
+ if: always()
+ run: |-
+ mkdir "app/build/test-results"
+ gsutil cp -r "gs://${{ steps.generate-dir.outputs.bucket }}/${{ steps.generate-dir.outputs.results_dir }}/Pixel2-30-en-portrait/test_result_1.xml" "app/build/test-results/results.xml"
+
+ - name: Upload tests results
+ if: always()
+ uses: actions/upload-artifact@v3
+ with:
+ name: test-results
+ path: |
+ */build/test-results/**
+ retention-days: 5
+
+ test-license-headers:
+ name: Ensure license headers are added
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3.11
+
+ - name: Ensure license headers are added
+ run: python "build-tools/check-license-headers.py"
+
+ test-results:
+ name: Upload tests results
+ runs-on: ubuntu-20.04
+ if: always()
+ needs:
+ - android-tests
+ - unit-tests
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ sparse-checkout: build-tools/
+
+ - name: Download tests results for both jobs
+ uses: actions/download-artifact@v3
+ with:
+ name: test-results
+
+ - id: auth
+ name: Authenticate to Google Cloud
+ uses: google-github-actions/auth@v1
+ with:
+ credentials_json: ${{ secrets.SERVICE_ACCOUNT }}
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3.11
+
+ - name: Install Python dependencies
+ uses: py-actions/py-dependency-install@v4
+ with:
+ path: "build-tools/requirements.txt"
+
+ - name: Upload to Big Query
+ run: |-
+ if [[ "${{ github.event_name }}" != "pull_request" ]]; then
+ python "build-tools/upload-junit-to-cloud.py" --url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" --final --glob "*/build/test-results/**/*.xml"
+ else
+ python "build-tools/upload-junit-to-cloud.py" --url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" --glob "*/build/test-results/**/*.xml"
+ fi
+
+ - name: Test Report
+ uses: dorny/test-reporter@v1
+ with:
+ name: Tests Results
+ path: "*/build/test-results/**/*.xml"
+ reporter: java-junit
+ fail-on-error: "false"
+
+ - name: Include Slack Notification
+ if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master'
+ uses: ./.github/actions/slack-notification
+ env:
+ SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
+ SLACK_GIT_REF: ${{ github.ref }}
+ SLACK_WORKFLOW: ${{ github.workflow }}
diff --git a/app-shared-tests/.gitignore b/app-shared-tests/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/app-shared-tests/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app-shared-tests/build.gradle b/app-shared-tests/build.gradle
new file mode 100644
index 000000000..ad6102989
--- /dev/null
+++ b/app-shared-tests/build.gradle
@@ -0,0 +1,98 @@
+plugins {
+ id 'com.android.library'
+ id 'kotlin-kapt'
+ id 'org.jetbrains.kotlin.android'
+ id 'com.google.dagger.hilt.android'
+ id 'org.jlleitschuh.gradle.ktlint' version '11.2.0'
+}
+
+android {
+ namespace 'com.appunite.loudius'
+ compileSdk 33
+
+ defaultConfig {
+ minSdk 24
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildFeatures {
+ compose true
+ }
+ packagingOptions {
+ resources {
+ excludes += 'META-INF/{AL2.0,LGPL2.1}'
+ excludes += 'META-INF/LICENSE.md'
+ excludes += 'META-INF/LICENSE-notice.md'
+ }
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.4.2'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ implementation(project(":app"))
+
+ // Hilt
+ implementation "com.google.dagger:hilt-android:2.45"
+ kapt "com.google.dagger:hilt-compiler:2.45"
+ api 'com.google.dagger:hilt-android-testing:2.45'
+
+ // assertion library
+ // cannot use 0.34.0 due to an existing bug
+ // https://github.com/robfletcher/strikt/issues/259
+ api 'io.strikt:strikt-core:0.33.0'
+ api 'io.strikt:strikt-mockk:0.33.0'
+
+ // Setup Junit4 and Junit5
+ api(platform("org.junit:junit-bom:5.10.0"))
+ api("org.junit.jupiter:junit-jupiter") {
+ because 'allows to write and run Jupiter tests'
+ }
+ api("junit:junit:4.13.2")
+ runtimeOnly("org.junit.vintage:junit-vintage-engine") {
+ because 'allows JUnit 3 and JUnit 4 tests to run'
+ }
+ runtimeOnly("org.junit.platform:junit-platform-launcher") {
+ because 'allows tests to run from IDEs that bundle older version of launcher'
+ }
+
+ //testing
+ api 'androidx.test:core-ktx:1.5.0'
+ api 'org.robolectric:robolectric:4.10.3'
+ api 'androidx.test.ext:junit-ktx:1.1.5'
+
+ api "io.mockk:mockk:1.13.3"
+
+ api "com.squareup.okhttp3:mockwebserver:4.10.0"
+
+ api 'androidx.test.ext:junit:1.1.5'
+ api 'androidx.test.espresso:espresso-core:3.5.1'
+ api "androidx.compose.ui:ui-test-junit4:$compose_version"
+
+ api 'androidx.test.espresso:espresso-intents:3.5.1'
+
+ // Firebase instrumentation lib
+ api 'com.google.firebase:testlab-instr-lib:0.2'
+
+ // ktlint
+ ktlintRuleset project(":custom-ktlint-rules")
+}
\ No newline at end of file
diff --git a/app-shared-tests/src/main/AndroidManifest.xml b/app-shared-tests/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..44008a433
--- /dev/null
+++ b/app-shared-tests/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/appunite/loudius/LoginScreenTest.kt b/app-shared-tests/src/main/java/com/appunite/loudius/AbsLoginScreenTest.kt
similarity index 91%
rename from app/src/androidTest/java/com/appunite/loudius/LoginScreenTest.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/AbsLoginScreenTest.kt
index 9cdd4869d..5888bcd5a 100644
--- a/app/src/androidTest/java/com/appunite/loudius/LoginScreenTest.kt
+++ b/app-shared-tests/src/main/java/com/appunite/loudius/AbsLoginScreenTest.kt
@@ -31,31 +31,22 @@ import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
import androidx.test.espresso.intent.rule.IntentsRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
import com.appunite.loudius.components.theme.LoudiusTheme
-import com.appunite.loudius.di.GithubHelperModule
import com.appunite.loudius.ui.login.GithubHelper
import com.appunite.loudius.ui.login.LoginScreen
import com.appunite.loudius.util.ScreenshotTestRule
-import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
-import dagger.hilt.android.testing.HiltAndroidTest
-import dagger.hilt.android.testing.UninstallModules
import io.mockk.every
import io.mockk.mockk
import org.hamcrest.Matchers.allOf
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
-@UninstallModules(GithubHelperModule::class)
-@HiltAndroidTest
-class LoginScreenTest {
+abstract class AbsLoginScreenTest {
@get:Rule(order = 0)
- val hiltRule = HiltAndroidRule(this)
+ val hiltRule by lazy { HiltAndroidRule(this) }
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule()
@@ -72,8 +63,6 @@ class LoginScreenTest {
hiltRule.inject()
}
- @BindValue
- @JvmField
val githubHelper: GithubHelper = mockk().apply {
every { shouldAskForXiaomiIntent() } returns false
}
@@ -90,6 +79,8 @@ class LoginScreenTest {
}
composeTestRule.onNodeWithText("Log in").performClick()
+
+ composeTestRule.waitForIdle()
intended(
allOf(
hasAction(Intent.ACTION_VIEW),
@@ -113,6 +104,7 @@ class LoginScreenTest {
composeTestRule.onNodeWithText("Log in").performClick()
composeTestRule.onNodeWithText("I've already granted").performClick()
+ composeTestRule.waitForIdle()
intended(
allOf(
hasAction(Intent.ACTION_VIEW),
@@ -136,6 +128,7 @@ class LoginScreenTest {
composeTestRule.onNodeWithText("Log in").performClick()
composeTestRule.onNodeWithText("Grant permission").performClick()
+ composeTestRule.waitForIdle()
intended(
allOf(
hasAction("miui.intent.action.APP_PERM_EDITOR"),
diff --git a/app/src/androidTest/java/com/appunite/loudius/PullRequestsScreenTest.kt b/app-shared-tests/src/main/java/com/appunite/loudius/AbsPullRequestsScreenTest.kt
similarity index 85%
rename from app/src/androidTest/java/com/appunite/loudius/PullRequestsScreenTest.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/AbsPullRequestsScreenTest.kt
index 9ebdbd54f..c36ca466d 100644
--- a/app/src/androidTest/java/com/appunite/loudius/PullRequestsScreenTest.kt
+++ b/app-shared-tests/src/main/java/com/appunite/loudius/AbsPullRequestsScreenTest.kt
@@ -18,23 +18,19 @@ package com.appunite.loudius
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithText
-import androidx.test.ext.junit.runners.AndroidJUnit4
import com.appunite.loudius.components.theme.LoudiusTheme
import com.appunite.loudius.ui.pullrequests.PullRequestsScreen
import com.appunite.loudius.util.IntegrationTestRule
import com.appunite.loudius.util.Register
-import dagger.hilt.android.testing.HiltAndroidTest
+import com.appunite.loudius.util.waitUntilLoadingDoesNotExist
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
-@HiltAndroidTest
-class PullRequestsScreenTest {
+abstract class AbsPullRequestsScreenTest {
@get:Rule
- val integrationTestRule = IntegrationTestRule(this)
+ val integrationTestRule by lazy { IntegrationTestRule(this) }
@Before
fun setUp() {
@@ -53,6 +49,8 @@ class PullRequestsScreenTest {
}
}
+ composeTestRule.waitUntilLoadingDoesNotExist()
+
composeTestRule.onNodeWithText("First Pull-Request title").assertIsDisplayed()
}
}
diff --git a/app/src/androidTest/java/com/appunite/loudius/ReviewersScreenTest.kt b/app-shared-tests/src/main/java/com/appunite/loudius/AbsReviewersScreenTest.kt
similarity index 86%
rename from app/src/androidTest/java/com/appunite/loudius/ReviewersScreenTest.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/AbsReviewersScreenTest.kt
index 69fbc1386..2fc4f33fd 100644
--- a/app/src/androidTest/java/com/appunite/loudius/ReviewersScreenTest.kt
+++ b/app-shared-tests/src/main/java/com/appunite/loudius/AbsReviewersScreenTest.kt
@@ -19,28 +19,24 @@ package com.appunite.loudius
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
-import androidx.test.ext.junit.runners.AndroidJUnit4
import com.appunite.loudius.components.theme.LoudiusTheme
import com.appunite.loudius.ui.reviewers.ReviewersScreen
import com.appunite.loudius.util.IntegrationTestRule
import com.appunite.loudius.util.Register
-import dagger.hilt.android.testing.HiltAndroidTest
+import com.appunite.loudius.util.waitUntilLoadingDoesNotExist
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
-@HiltAndroidTest
-class ReviewersScreenTest {
+abstract class AbsReviewersScreenTest {
@get:Rule
- val integrationTestRule = IntegrationTestRule(this)
+ val integrationTestRule by lazy { IntegrationTestRule(this) }
@Before
fun setUp() {
- integrationTestRule.setUp()
integrationTestRule.initTests()
+ integrationTestRule.setUp()
}
@Test
@@ -51,6 +47,9 @@ class ReviewersScreenTest {
ReviewersScreen { }
}
}
+
+ composeTestRule.waitUntilLoadingDoesNotExist()
+
composeTestRule.onNodeWithText("userLogin").assertIsDisplayed()
}
}
@@ -65,7 +64,13 @@ class ReviewersScreenTest {
ReviewersScreen { }
}
}
+
+ composeTestRule.waitUntilLoadingDoesNotExist()
+
composeTestRule.onNodeWithText("Notify").performClick()
+
+ composeTestRule.waitUntilLoadingDoesNotExist()
+
composeTestRule
.onNodeWithText("Awesome! Your collaborator have been pinged for some serious code review action! \uD83C\uDF89")
.assertIsDisplayed()
@@ -80,7 +85,13 @@ class ReviewersScreenTest {
ReviewersScreen { }
}
}
+
+ composeTestRule.waitUntilLoadingDoesNotExist()
+
composeTestRule.onNodeWithText("Notify").performClick()
+
+ composeTestRule.waitUntilLoadingDoesNotExist()
+
composeTestRule
.onNodeWithText("Uh-oh, it seems that Loudius has taken a vacation. Don't worry, we're sending a postcard to bring it back ASAP!")
.assertIsDisplayed()
diff --git a/app/src/androidTest/java/com/appunite/loudius/WalkThroughAppTest.kt b/app-shared-tests/src/main/java/com/appunite/loudius/AbsWalkThroughAppTest.kt
similarity index 79%
rename from app/src/androidTest/java/com/appunite/loudius/WalkThroughAppTest.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/AbsWalkThroughAppTest.kt
index a8293ba40..682009090 100644
--- a/app/src/androidTest/java/com/appunite/loudius/WalkThroughAppTest.kt
+++ b/app-shared-tests/src/main/java/com/appunite/loudius/AbsWalkThroughAppTest.kt
@@ -24,27 +24,29 @@ import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ActivityScenario
-import androidx.test.espresso.intent.Intents
+import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.intent.rule.IntentsRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
import com.appunite.loudius.util.IntegrationTestRule
import com.appunite.loudius.util.Register
-import dagger.hilt.android.testing.HiltAndroidTest
+import com.appunite.loudius.util.waitUntilLoadingDoesNotExist
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import org.junit.runner.RunWith
-@RunWith(AndroidJUnit4::class)
-@HiltAndroidTest
-class WalkThroughAppTest {
+abstract class AbsWalkThroughAppTest {
@get:Rule(order = 0)
- val integrationTestRule = IntegrationTestRule(this, MainActivity::class.java)
+ val integrationTestRule by lazy { IntegrationTestRule(this, MainActivity::class.java) }
@get:Rule(order = 1)
val intents = IntentsRule()
+ @Before
+ fun setUp() {
+ integrationTestRule.setUp()
+ }
+
@Test
fun whenLoginScreenIsVisible_LoginButtonOpensGithubAuth(): Unit = with(integrationTestRule) {
Register.run {
@@ -56,7 +58,7 @@ class WalkThroughAppTest {
comment(mockWebServer)
}
- Intents.intending(IntentMatchers.hasData("https://github.com/login/oauth/authorize?client_id=91131449e417c7e29912&scope=repo"))
+ intending(IntentMatchers.hasData("https://github.com/login/oauth/authorize?client_id=91131449e417c7e29912&scope=repo"))
.respondWithFunction {
Instrumentation.ActivityResult(Activity.RESULT_OK, null)
}
@@ -73,9 +75,16 @@ class WalkThroughAppTest {
},
)
+ composeTestRule.waitUntilLoadingDoesNotExist()
+
composeTestRule.onNodeWithText("First Pull-Request title").performClick()
+ composeTestRule.waitUntilLoadingDoesNotExist()
+
composeTestRule.onNodeWithText("Notify").performClick()
+
+ composeTestRule.waitUntilLoadingDoesNotExist()
+
composeTestRule
.onNodeWithText("Awesome! Your collaborator have been pinged for some serious code review action! \uD83C\uDF89")
.assertIsDisplayed()
diff --git a/app/src/debug/java/com/appunite/loudius/TestActivity.kt b/app-shared-tests/src/main/java/com/appunite/loudius/TestActivity.kt
similarity index 100%
rename from app/src/debug/java/com/appunite/loudius/TestActivity.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/TestActivity.kt
diff --git a/app/src/androidTest/java/com/appunite/loudius/util/Assertions.kt b/app-shared-tests/src/main/java/com/appunite/loudius/util/Assertions.kt
similarity index 100%
rename from app/src/androidTest/java/com/appunite/loudius/util/Assertions.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/util/Assertions.kt
diff --git a/app-shared-tests/src/main/java/com/appunite/loudius/util/ComposeUiTestHelpers.kt b/app-shared-tests/src/main/java/com/appunite/loudius/util/ComposeUiTestHelpers.kt
new file mode 100644
index 000000000..048fd584b
--- /dev/null
+++ b/app-shared-tests/src/main/java/com/appunite/loudius/util/ComposeUiTestHelpers.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalTestApi::class)
+
+package com.appunite.loudius.util
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.hasStateDescription
+import androidx.compose.ui.test.junit4.AndroidComposeTestRule
+
+fun AndroidComposeTestRule<*, *>.waitUntilLoadingDoesNotExist() {
+ waitUntilDoesNotExist(hasStateDescription("Loading data…"), 10_000L)
+}
diff --git a/app/src/androidTest/java/com/appunite/loudius/util/IdlingResourceExtensions.kt b/app-shared-tests/src/main/java/com/appunite/loudius/util/IdlingResourceExtensions.kt
similarity index 100%
rename from app/src/androidTest/java/com/appunite/loudius/util/IdlingResourceExtensions.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/util/IdlingResourceExtensions.kt
diff --git a/app/src/androidTest/java/com/appunite/loudius/util/IntegrationTestRule.kt b/app-shared-tests/src/main/java/com/appunite/loudius/util/IntegrationTestRule.kt
similarity index 100%
rename from app/src/androidTest/java/com/appunite/loudius/util/IntegrationTestRule.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/util/IntegrationTestRule.kt
diff --git a/app/src/androidTest/java/com/appunite/loudius/util/MockWebServerRule.kt b/app-shared-tests/src/main/java/com/appunite/loudius/util/MockWebServerRule.kt
similarity index 100%
rename from app/src/androidTest/java/com/appunite/loudius/util/MockWebServerRule.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/util/MockWebServerRule.kt
diff --git a/app/src/androidTest/java/com/appunite/loudius/util/Register.kt b/app-shared-tests/src/main/java/com/appunite/loudius/util/Register.kt
similarity index 100%
rename from app/src/androidTest/java/com/appunite/loudius/util/Register.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/util/Register.kt
diff --git a/app/src/androidTest/java/com/appunite/loudius/util/ScreenshotTestRule.kt b/app-shared-tests/src/main/java/com/appunite/loudius/util/ScreenshotTestRule.kt
similarity index 94%
rename from app/src/androidTest/java/com/appunite/loudius/util/ScreenshotTestRule.kt
rename to app-shared-tests/src/main/java/com/appunite/loudius/util/ScreenshotTestRule.kt
index 5725e2074..7b56f36ef 100644
--- a/app/src/androidTest/java/com/appunite/loudius/util/ScreenshotTestRule.kt
+++ b/app-shared-tests/src/main/java/com/appunite/loudius/util/ScreenshotTestRule.kt
@@ -31,6 +31,10 @@ import java.util.concurrent.atomic.AtomicBoolean
open class ScreenshotTestRule : TestRule {
override fun apply(base: Statement, description: Description): Statement {
+ if (!isAndroidTest) {
+ // Only with AndroidTest we can do screenshots, otherwise ignore this rule
+ return base
+ }
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
diff --git a/app-shared-tests/src/main/java/com/appunite/loudius/util/TestType.kt b/app-shared-tests/src/main/java/com/appunite/loudius/util/TestType.kt
new file mode 100644
index 000000000..983015194
--- /dev/null
+++ b/app-shared-tests/src/main/java/com/appunite/loudius/util/TestType.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius.util
+
+import java.util.Locale
+
+/**
+ * Returns true if test is androidTest, returns false if this is unit test or robolectric test
+ */
+val isAndroidTest =
+ System.getProperty("java.runtime.name")
+ ?.lowercase(Locale.US)
+ ?.contains("android")
+ ?: false
diff --git a/app/src/androidTest/java/com/appunite/loudius/util/MockWebServerRuleTest.kt b/app-shared-tests/src/test/java/com/appunite/loudius/util/MockWebServerRuleTest.kt
similarity index 94%
rename from app/src/androidTest/java/com/appunite/loudius/util/MockWebServerRuleTest.kt
rename to app-shared-tests/src/test/java/com/appunite/loudius/util/MockWebServerRuleTest.kt
index b583cc3c1..03b499d6b 100644
--- a/app/src/androidTest/java/com/appunite/loudius/util/MockWebServerRuleTest.kt
+++ b/app-shared-tests/src/test/java/com/appunite/loudius/util/MockWebServerRuleTest.kt
@@ -16,11 +16,12 @@
package com.appunite.loudius.util
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.util.Log
import com.appunite.loudius.di.TestInterceptor
import io.mockk.CapturingSlot
import io.mockk.every
import io.mockk.mockk
+import io.mockk.mockkStatic
import io.mockk.slot
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
@@ -32,7 +33,7 @@ import org.hamcrest.TypeSafeMatcher
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
-import org.junit.runner.RunWith
+import org.junit.rules.TestWatcher
import strikt.api.expectThat
import strikt.assertions.contains
import strikt.assertions.first
@@ -45,7 +46,6 @@ import strikt.assertions.message
import strikt.assertions.single
import strikt.mockk.captured
-@RunWith(AndroidJUnit4::class)
class MockWebServerRuleTest {
@Suppress("DEPRECATION")
@@ -53,6 +53,9 @@ class MockWebServerRuleTest {
val expectedException: ExpectedException = ExpectedException.none()
@get:Rule(order = 1)
+ val loggerMockRule: LoggerMockRule = LoggerMockRule()
+
+ @get:Rule(order = 2)
val mockWebServerRule: MockWebServerRule = MockWebServerRule()
@Test
@@ -304,3 +307,14 @@ private fun matcher(check: (T) -> Unit): Matcher = object : TypeSafeMatch
}
}
}
+
+class LoggerMockRule : TestWatcher() {
+ override fun starting(description: org.junit.runner.Description?) {
+ mockkStatic(Log::class)
+ every { Log.v(any(), any()) } returns 0
+ every { Log.v(any(), any(), any()) } returns 0
+ every { Log.w(any(), any()) } returns 0
+ every { Log.w(any(), any()) } returns 0
+ every { Log.w(any(), any(), any()) } returns 0
+ }
+}
diff --git a/app/build.gradle b/app/build.gradle
index 50a1887a5..8545d93a6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -64,6 +64,18 @@ android {
useLegacyPackaging true
}
}
+ unitTests {
+ includeAndroidResources = true
+ animationsDisabled = true
+ all {
+ // Disable tests on release because on release we don't have TestActivity.
+ // We could also filter tasks by filter {}, but right now we don't need to test
+ // release version.
+ if (it.name == 'testReleaseUnitTest') {
+ it.enabled = false
+ }
+ }
+ }
}
kapt {
arguments {
@@ -73,7 +85,7 @@ android {
}
dependencies {
- implementation project(':components')
+ api project(':components')
//Desugaring for use of java.time in api lower then 26
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2'
@@ -104,7 +116,6 @@ dependencies {
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
//DI - for local unit tests
- testImplementation 'com.google.dagger:hilt-android-testing:2.45'
kaptTest 'com.google.dagger:hilt-compiler:2.45'
// DI - For instrumented tests.
@@ -129,29 +140,11 @@ dependencies {
//gson
implementation 'com.google.code.gson:gson:2.10.1'
- // assertion library
- // cannot use 0.34.0 due to an existing bug
- // https://github.com/robfletcher/strikt/issues/259
- testImplementation 'io.strikt:strikt-core:0.33.0'
- testImplementation 'io.strikt:strikt-mockk:0.33.0'
- androidTestImplementation 'io.strikt:strikt-core:0.33.0'
- androidTestImplementation 'io.strikt:strikt-mockk:0.33.0'
-
- //testing
- testImplementation "io.mockk:mockk:1.13.3"
- testImplementation("com.squareup.okhttp3:mockwebserver:4.10.0")
- testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
- testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
- androidTestImplementation 'androidx.test.ext:junit:1.1.5'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
- androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
- androidTestImplementation("com.squareup.okhttp3:mockwebserver:4.11.0")
- androidTestImplementation("io.mockk:mockk-android:1.13.3")
-
- androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
-
- // Firebase instrumentation lib
- androidTestImplementation 'com.google.firebase:testlab-instr-lib:0.2'
+ testImplementation project(":app-shared-tests")
+ androidTestImplementation(project(":app-shared-tests")) {
+ exclude group: 'org.robolectric', module: 'robolectric'
+ }
+ androidTestImplementation "io.mockk:mockk-android:1.13.3"
// ktlint
ktlintRuleset project(":custom-ktlint-rules")
diff --git a/app/src/androidTest/java/com/appunite/loudius/IntegrationLoginScreenTest.kt b/app/src/androidTest/java/com/appunite/loudius/IntegrationLoginScreenTest.kt
new file mode 100644
index 000000000..3a1f089f8
--- /dev/null
+++ b/app/src/androidTest/java/com/appunite/loudius/IntegrationLoginScreenTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.appunite.loudius.di.GithubHelperModule
+import dagger.hilt.android.testing.BindValue
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.UninstallModules
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@HiltAndroidTest
+@UninstallModules(GithubHelperModule::class)
+class IntegrationLoginScreenTest : AbsLoginScreenTest() {
+
+ @BindValue @JvmField
+ val githubHelperBind = githubHelper
+}
diff --git a/app/src/androidTest/java/com/appunite/loudius/IntegrationPullRequestsScreenTest.kt b/app/src/androidTest/java/com/appunite/loudius/IntegrationPullRequestsScreenTest.kt
new file mode 100644
index 000000000..df95c10d1
--- /dev/null
+++ b/app/src/androidTest/java/com/appunite/loudius/IntegrationPullRequestsScreenTest.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.hilt.android.testing.HiltAndroidTest
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@HiltAndroidTest
+class IntegrationPullRequestsScreenTest : AbsPullRequestsScreenTest()
diff --git a/app/src/androidTest/java/com/appunite/loudius/IntegrationReviewersScreenTest.kt b/app/src/androidTest/java/com/appunite/loudius/IntegrationReviewersScreenTest.kt
new file mode 100644
index 000000000..f23e5c261
--- /dev/null
+++ b/app/src/androidTest/java/com/appunite/loudius/IntegrationReviewersScreenTest.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 owner S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.hilt.android.testing.HiltAndroidTest
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@HiltAndroidTest
+class IntegrationReviewersScreenTest : AbsReviewersScreenTest()
diff --git a/app/src/androidTest/java/com/appunite/loudius/IntegrationWalkThroughAppTest.kt b/app/src/androidTest/java/com/appunite/loudius/IntegrationWalkThroughAppTest.kt
new file mode 100644
index 000000000..24baaf7bc
--- /dev/null
+++ b/app/src/androidTest/java/com/appunite/loudius/IntegrationWalkThroughAppTest.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.hilt.android.testing.HiltAndroidTest
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@HiltAndroidTest
+class IntegrationWalkThroughAppTest : AbsWalkThroughAppTest()
diff --git a/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsScreen.kt b/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsScreen.kt
index 71eaa7eed..4130cb24c 100644
--- a/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsScreen.kt
+++ b/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsScreen.kt
@@ -33,7 +33,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@@ -64,7 +63,7 @@ fun PullRequestsScreen(
navigateToReviewers: NavigateToReviewers,
) {
val state = viewModel.state
- val refreshing by viewModel.isRefreshing.collectAsState()
+ val refreshing = viewModel.isRefreshing
PullRequestsScreenStateless(
state = state,
diff --git a/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt b/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt
index 3dce46ac3..7ef83c4a5 100644
--- a/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt
+++ b/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt
@@ -24,8 +24,6 @@ import androidx.lifecycle.viewModelScope
import com.appunite.loudius.domain.repository.PullRequestRepository
import com.appunite.loudius.network.model.PullRequest
import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -60,8 +58,8 @@ class PullRequestsViewModel @Inject constructor(
var state: PullRequestState by mutableStateOf(PullRequestState())
private set
- private val _isRefreshing = MutableStateFlow(false)
- val isRefreshing = _isRefreshing.asStateFlow()
+ var isRefreshing: Boolean by mutableStateOf(false)
+ private set
init {
fetchData()
@@ -69,14 +67,14 @@ class PullRequestsViewModel @Inject constructor(
fun refreshData() {
viewModelScope.launch {
- _isRefreshing.value = true
+ isRefreshing = true
pullRequestsRepository.getCurrentUserPullRequests()
.onSuccess {
state = state.copy(data = Data.Success(it.items))
}.onFailure {
state = state.copy(data = Data.Error)
}
- _isRefreshing.value = false
+ isRefreshing = false
}
}
diff --git a/app/src/test/java/com/appunite/loudius/ActivitySetupTest.kt b/app/src/test/java/com/appunite/loudius/ActivitySetupTest.kt
new file mode 100644
index 000000000..4e2f20653
--- /dev/null
+++ b/app/src/test/java/com/appunite/loudius/ActivitySetupTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius
+
+import android.os.Build
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.hilt.android.testing.HiltAndroidRule
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.HiltTestApplication
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.jupiter.api.DisplayName
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(AndroidJUnit4::class)
+@Config(sdk = [Build.VERSION_CODES.Q], application = HiltTestApplication::class)
+@DisplayName("ensure activity tests are set correctly")
+@HiltAndroidTest
+class ActivitySetupTest {
+
+ @get:Rule(order = 0)
+ val hiltRule by lazy { HiltAndroidRule(this) }
+
+ @Before
+ fun setUp() {
+ Assume.assumeTrue(BuildConfig.DEBUG)
+ hiltRule.inject()
+ }
+
+ @Test
+ fun `ensure test activity can be started during tests`() {
+ ActivityScenario.launch(TestActivity::class.java)
+ }
+}
diff --git a/app/src/test/java/com/appunite/loudius/RobolectricSetupTest.kt b/app/src/test/java/com/appunite/loudius/RobolectricSetupTest.kt
new file mode 100644
index 000000000..b013eb636
--- /dev/null
+++ b/app/src/test/java/com/appunite/loudius/RobolectricSetupTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius
+
+import android.content.Context
+import android.os.Build
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.jupiter.api.DisplayName
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import strikt.api.expectThat
+import strikt.assertions.contains
+
+@RunWith(AndroidJUnit4::class)
+@Config(sdk = [Build.VERSION_CODES.Q])
+@DisplayName("ensure robolectric tests setup is set correctly")
+class RobolectricSetupTest {
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun `ensure context is mocked correctly`() {
+ expectThat(context.packageName).contains("com.appunite.loudius")
+ }
+}
diff --git a/app/src/test/java/com/appunite/loudius/UnitLoginScreenTest.kt b/app/src/test/java/com/appunite/loudius/UnitLoginScreenTest.kt
new file mode 100644
index 000000000..bdda93651
--- /dev/null
+++ b/app/src/test/java/com/appunite/loudius/UnitLoginScreenTest.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.appunite.loudius.di.GithubHelperModule
+import dagger.hilt.android.testing.BindValue
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.HiltTestApplication
+import dagger.hilt.android.testing.UninstallModules
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(AndroidJUnit4::class)
+@Config(sdk = [Build.VERSION_CODES.Q], application = HiltTestApplication::class)
+@HiltAndroidTest
+@UninstallModules(GithubHelperModule::class)
+class UnitLoginScreenTest : AbsLoginScreenTest() {
+
+ @BindValue @JvmField
+ val githubHelperBind = githubHelper
+}
diff --git a/app/src/test/java/com/appunite/loudius/UnitPullRequestsScreenTest.kt b/app/src/test/java/com/appunite/loudius/UnitPullRequestsScreenTest.kt
new file mode 100644
index 000000000..9fedb0dce
--- /dev/null
+++ b/app/src/test/java/com/appunite/loudius/UnitPullRequestsScreenTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalTestApi::class)
+
+package com.appunite.loudius
+
+import android.os.Build
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.HiltTestApplication
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(AndroidJUnit4::class)
+@Config(sdk = [Build.VERSION_CODES.Q], application = HiltTestApplication::class)
+@HiltAndroidTest
+class UnitPullRequestsScreenTest : AbsPullRequestsScreenTest()
diff --git a/app/src/test/java/com/appunite/loudius/UnitReviewersScreenTest.kt b/app/src/test/java/com/appunite/loudius/UnitReviewersScreenTest.kt
new file mode 100644
index 000000000..78425bfd3
--- /dev/null
+++ b/app/src/test/java/com/appunite/loudius/UnitReviewersScreenTest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 owner S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.HiltTestApplication
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(AndroidJUnit4::class)
+@HiltAndroidTest
+@Config(sdk = [Build.VERSION_CODES.Q], application = HiltTestApplication::class)
+class UnitReviewersScreenTest : AbsReviewersScreenTest()
diff --git a/app/src/test/java/com/appunite/loudius/UnitWalkThroughAppTest.kt b/app/src/test/java/com/appunite/loudius/UnitWalkThroughAppTest.kt
new file mode 100644
index 000000000..ce9707794
--- /dev/null
+++ b/app/src/test/java/com/appunite/loudius/UnitWalkThroughAppTest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.HiltTestApplication
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(AndroidJUnit4::class)
+@HiltAndroidTest
+@Config(sdk = [Build.VERSION_CODES.Q], application = HiltTestApplication::class)
+class UnitWalkThroughAppTest : AbsWalkThroughAppTest()
diff --git a/app/src/test/java/com/appunite/loudius/fakes/FakeUserLocalDataSource.kt b/app/src/test/java/com/appunite/loudius/fakes/FakeUserLocalDataSource.kt
index 67a5a8240..7f5860893 100644
--- a/app/src/test/java/com/appunite/loudius/fakes/FakeUserLocalDataSource.kt
+++ b/app/src/test/java/com/appunite/loudius/fakes/FakeUserLocalDataSource.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.appunite.loudius.fakes
import com.appunite.loudius.domain.store.UserLocalDataSource
diff --git a/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt b/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt
index b0b20502e..919f4cfad 100644
--- a/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt
+++ b/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt
@@ -52,7 +52,7 @@ class PullRequestsViewModelTest {
viewModel.refreshData()
- expectThat(viewModel.isRefreshing.value).isTrue()
+ expectThat(viewModel.isRefreshing).isTrue()
}
@Test
@@ -61,7 +61,7 @@ class PullRequestsViewModelTest {
viewModel.refreshData()
- expectThat(viewModel.isRefreshing.value).isFalse()
+ expectThat(viewModel.isRefreshing).isFalse()
}
@Test
diff --git a/build-tools/check-license-headers.py b/build-tools/check-license-headers.py
new file mode 100644
index 000000000..1663bb98f
--- /dev/null
+++ b/build-tools/check-license-headers.py
@@ -0,0 +1,70 @@
+# Copyright 2023 AppUnite S.A.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import fnmatch
+import re
+import sys
+import typing
+import subprocess
+
+
+def get_tracked_files() -> typing.List[str]:
+ """Returns a list of all files tracked by Git."""
+
+ cmd = ["git", "ls-files"]
+ output = subprocess.check_output(cmd).decode("utf-8")
+ return output.splitlines()
+
+
+def check_license(file: str) -> bool:
+ with open(file, "r") as f:
+ contents = f.read()
+ return "Licensed under the Apache License" in contents
+
+
+matchers = [
+ re.compile(fnmatch.translate('*.kt')),
+ re.compile(fnmatch.translate('*.py')),
+]
+
+
+def should_check(file: str) -> bool:
+ for matcher in matchers:
+ if matcher.match(file):
+ return True
+ return False
+
+
+def main():
+ files = get_tracked_files()
+
+ errors: typing.List[str] = []
+ for file in files:
+ if should_check(file):
+ if not check_license(file):
+ errors.append(f"❌ File \"{file}\" does not contain the license phrase")
+ else:
+ print(f"ℹ️ Skipping check for \"{file}\"")
+
+ for error in errors:
+ print(error, file=sys.stderr)
+ if errors:
+ exit(1)
+ else:
+ print("✅ All files contain the license phrase")
+ exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/build-tools/upload-junit-to-cloud.py b/build-tools/upload-junit-to-cloud.py
index d1fdba10f..be211955d 100644
--- a/build-tools/upload-junit-to-cloud.py
+++ b/build-tools/upload-junit-to-cloud.py
@@ -1,30 +1,66 @@
+# Copyright 2023 AppUnite S.A.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import typing
+
from google.cloud import bigquery
import xml.etree.ElementTree as ET
import argparse
+from typing import List, Optional
+import glob
+
# Uploading JUnit test results to BigQuery
-def upload(final: bool):
+def upload(final: bool, dummy: bool, url: Optional[str], files: List[typing.TextIO]):
client = bigquery.Client()
dataset_id = 'test_results'
table_id = 'my_table'
- tree = ET.parse('build/test-results/results.xml')
- root = tree.getroot()
- timestamp = root.attrib['timestamp']
-
rows = []
- for testcase in root.iter('testcase'):
- success = len(testcase.findall('failure')) == 0
- row = {
- 'timestamp': timestamp,
- 'testcase_final': final,
- 'testcase_name': testcase.attrib['name'],
- 'testcase_classname': testcase.attrib['classname'],
- 'testcase_time': float(testcase.attrib['time']),
- 'testcase_status_success': success
- }
- rows.append(row)
+ for file in files:
+ tree = ET.parse(file)
+ root = tree.getroot()
+ timestamp = root.attrib['timestamp']
+ test_cases = 0
+
+ for testcase in root.iter('testcase'):
+ success = len(testcase.findall('failure')) == 0
+ failures = []
+ for failure in testcase.findall('failure'):
+ failures.append({
+ 'message': failure.attrib.get('message', ''),
+ 'type': failure.attrib.get('type', ''),
+ 'content': failure.text,
+ })
+ row = {
+ 'timestamp': timestamp,
+ 'testcase_url': url,
+ 'testcase_final': final,
+ 'testcase_name': testcase.attrib['name'],
+ 'testcase_classname': testcase.attrib['classname'],
+ 'testcase_time': float(testcase.attrib['time']),
+ 'testcase_status_success': success,
+ 'testcase_failures': failures,
+ }
+ rows.append(row)
+ test_cases += 1
+ print(f"Read \"{file.name}\" file with {test_cases} tests")
+
+ if dummy:
+ print("Exiting without actions")
+ return
dataset_ref = client.dataset(dataset_id)
dataset = bigquery.Dataset(dataset_ref)
@@ -35,7 +71,13 @@ def upload(final: bool):
bigquery.SchemaField('testcase_name', 'STRING', mode='REQUIRED'),
bigquery.SchemaField('testcase_classname', 'STRING', mode='REQUIRED'),
bigquery.SchemaField('testcase_time', 'FLOAT', mode='REQUIRED'),
- bigquery.SchemaField('testcase_status_success', 'BOOLEAN', mode='REQUIRED')
+ bigquery.SchemaField('testcase_status_success', 'BOOLEAN', mode='REQUIRED'),
+ bigquery.SchemaField('testcase_url', 'STRING', mode='NULLABLE'),
+ bigquery.SchemaField('testcase_failures', 'RECORD', mode='REPEATED', fields=[
+ bigquery.SchemaField('message', 'STRING', mode='NULLABLE'),
+ bigquery.SchemaField('type', 'STRING', mode='NULLABLE'),
+ bigquery.SchemaField('content', 'STRING', mode='NULLABLE'),
+ ]),
]
table = bigquery.Table(table_ref, schema=schema)
@@ -48,6 +90,7 @@ def table_exists(table_ref):
return False
else:
raise e
+
if not table_exists(table_ref):
client.create_table(table)
@@ -60,6 +103,24 @@ def table_exists(table_ref):
parser = argparse.ArgumentParser()
parser.add_argument('--final', action='store_true', help='Enable final mode')
+parser.add_argument('--dummy', action='store_true', help='Do not upload data')
+parser.add_argument('--url')
+parser.add_argument('--glob', type=str, required=False)
+parser.add_argument('file', type=argparse.FileType('r'), nargs='*')
args = parser.parse_args()
-upload(args.final)
\ No newline at end of file
+all_files: List[typing.TextIO] = args.file
+if args.glob:
+ glob_files = glob.glob(args.glob, recursive=True)
+ if not glob_files:
+ parser.error(f"Could not find any file matching {args.glob}")
+ glob_open_files = [open(file, "r") for file in glob_files]
+ all_files.extend(glob_open_files)
+if not all_files:
+ parser.error(f"You need to specify --glob or file to upload")
+upload(
+ final=args.final,
+ dummy=args.dummy,
+ url=args.url,
+ files=args.file
+)
diff --git a/components/build.gradle b/components/build.gradle
index ec2b1dcbe..82446ec6c 100644
--- a/components/build.gradle
+++ b/components/build.gradle
@@ -3,6 +3,7 @@ plugins {
id 'org.jetbrains.kotlin.android'
id 'app.cash.paparazzi'
id 'kotlin-kapt'
+ id 'org.jlleitschuh.gradle.ktlint' version '11.2.0'
}
android {
@@ -57,4 +58,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+
+ // ktlint
+ ktlintRuleset project(":custom-ktlint-rules")
}
diff --git a/components/src/main/java/com/appunite/loudius/components/components/LoudiusLoadingIndicator.kt b/components/src/main/java/com/appunite/loudius/components/components/LoudiusLoadingIndicator.kt
index 412cc8b24..d06a5aae9 100644
--- a/components/src/main/java/com/appunite/loudius/components/components/LoudiusLoadingIndicator.kt
+++ b/components/src/main/java/com/appunite/loudius/components/components/LoudiusLoadingIndicator.kt
@@ -23,6 +23,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.airbnb.lottie.compose.LottieAnimation
@@ -41,8 +44,13 @@ fun LoudiusLoadingIndicator(modifier: Modifier = Modifier) {
composition = composition,
iterations = LottieConstants.IterateForever,
)
+ val loadingContentDescription = stringResource(R.string.components_loading_indicator_content_description)
Box(
- modifier = modifier.fillMaxSize(),
+ modifier = modifier
+ .fillMaxSize()
+ .semantics(mergeDescendants = true) {
+ stateDescription = loadingContentDescription
+ },
) {
LottieAnimation(
composition = composition,
diff --git a/components/src/main/java/com/appunite/loudius/components/components/utils/ReferenceDevices.kt b/components/src/main/java/com/appunite/loudius/components/components/utils/ReferenceDevices.kt
index 4615af329..d8214f2ef 100644
--- a/components/src/main/java/com/appunite/loudius/components/components/utils/ReferenceDevices.kt
+++ b/components/src/main/java/com/appunite/loudius/components/components/utils/ReferenceDevices.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.appunite.loudius.components.components.utils
import androidx.compose.ui.tooling.preview.Devices
diff --git a/components/src/main/res/values/strings.xml b/components/src/main/res/values/strings.xml
index 6fc2a5bd4..440a9cf55 100644
--- a/components/src/main/res/values/strings.xml
+++ b/components/src/main/res/values/strings.xml
@@ -2,6 +2,9 @@
Back button
+
+ Loading data…
+
Error
Something went wrong…
diff --git a/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/CustomRuleSetProvider.kt b/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/CustomRuleSetProvider.kt
index 0fc49db48..fceb5c34c 100644
--- a/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/CustomRuleSetProvider.kt
+++ b/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/CustomRuleSetProvider.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.appunite.loudius.rules
import com.pinterest.ktlint.core.RuleProvider
@@ -9,5 +25,6 @@ class CustomRuleSetProvider : RuleSetProviderV2(id = RULE_SET_ID, about = NO_ABO
override fun getRuleProviders(): Set =
setOf(
RuleProvider { UseStriktAssertionLibrary() },
+ RuleProvider { DoNotMixJunitVersions() },
)
}
diff --git a/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/DoNotMixJunitVersions.kt b/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/DoNotMixJunitVersions.kt
new file mode 100644
index 000000000..55465e3ec
--- /dev/null
+++ b/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/DoNotMixJunitVersions.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius.rules
+
+import com.pinterest.ktlint.core.Rule
+import com.pinterest.ktlint.core.ast.ElementType
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.psi.KtImportDirective
+
+class DoNotMixJunitVersions : Rule("do-not-mix-junit-versions") {
+ val junit4Annotations = listOf(
+ "org.junit.Test",
+ "org.junit.Before",
+ "org.junit.After",
+ "org.junit.Ignore",
+ "org.junit.runner.RunWith",
+ "org.junit.runners.Parameterized",
+ "org.junit.runners.Theory",
+ )
+ val junit5Annotations = listOf(
+ "org.junit.jupiter.api.Test",
+ "org.junit.jupiter.api.BeforeEach",
+ "org.junit.jupiter.api.AfterEach",
+ "org.junit.jupiter.api.Disabled",
+ "org.junit.jupiter.api.extension.ExtendWith",
+ "org.junit.jupiter.params.ParameterizedTest",
+ "org.junit.jupiter.params.provider.ValueSource",
+ )
+
+ override fun beforeVisitChildNodes(
+ node: ASTNode,
+ autoCorrect: Boolean,
+ emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
+ ) {
+ if (node.elementType == ElementType.IMPORT_LIST) {
+ val children = node.getChildren(null)
+ if (children.isNotEmpty()) {
+ val imports = children
+ .filter { it.elementType == ElementType.IMPORT_DIRECTIVE }
+ .mapNotNull { it.psi as? KtImportDirective }
+ .mapNotNull { it.importPath?.pathStr }
+
+ val junit4Imports = imports.filter { junit4Annotations.contains(it) }
+ val junit5Imports = imports.filter { junit5Annotations.contains(it) }
+
+ if (junit4Imports.isNotEmpty() && junit5Imports.isNotEmpty()) {
+ val errorMessage = "${junit4Imports.joinToString(separator = ",")} " +
+ "and ${junit5Imports.joinToString(separator = ",")} " +
+ "packages are from different JUnit versions. Don't mix Junit4 with Junit5 in a single test."
+ emit(
+ node.startOffset,
+ errorMessage,
+ false,
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/UseStriktAssertionLibrary.kt b/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/UseStriktAssertionLibrary.kt
index e84378dea..2ce456173 100644
--- a/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/UseStriktAssertionLibrary.kt
+++ b/custom-ktlint-rules/src/main/kotlin/com/appunite/loudius/rules/UseStriktAssertionLibrary.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.appunite.loudius.rules
import com.pinterest.ktlint.core.Rule
diff --git a/custom-ktlint-rules/src/test/kotlin/com/appunite/loudius/rules/DoNotMixJunitVersionsTest.kt b/custom-ktlint-rules/src/test/kotlin/com/appunite/loudius/rules/DoNotMixJunitVersionsTest.kt
new file mode 100644
index 000000000..6607d1b1e
--- /dev/null
+++ b/custom-ktlint-rules/src/test/kotlin/com/appunite/loudius/rules/DoNotMixJunitVersionsTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.appunite.loudius.rules
+
+import com.pinterest.ktlint.test.KtLintAssertThat
+import org.junit.Test
+
+class DoNotMixJunitVersionsTest {
+
+ private val wrappingRuleAssertThat =
+ KtLintAssertThat.assertThatRule { DoNotMixJunitVersions() }
+
+ @Test
+ fun `allows standard imports`() {
+ //language=kotlin
+ val code =
+ """
+ import a.b.c
+ import foo.bar
+ """.trimIndent()
+
+ wrappingRuleAssertThat(code).hasNoLintViolations()
+ }
+
+ @Test
+ fun `allows Junit4 imports`() {
+ //language=kotlin
+ val code =
+ """
+ import org.junit.Test
+ import org.junit.Before
+ import org.junit.After
+ import org.junit.Ignore
+ import org.junit.runner.RunWith
+ import org.junit.runners.Parameterized
+ import org.junit.runners.Theor
+ """.trimIndent()
+
+ wrappingRuleAssertThat(code).hasNoLintViolations()
+ }
+
+ @Test
+ fun `allows Junit5 imports`() {
+ //language=kotlin
+ val code =
+ """
+ import org.junit.jupiter.api.Test
+ import org.junit.jupiter.api.BeforeEach
+ import org.junit.jupiter.api.AfterEach
+ import org.junit.jupiter.api.Disabled
+ import org.junit.jupiter.api.extension.ExtendWith
+ import org.junit.jupiter.params.ParameterizedTest
+ import org.junit.jupiter.params.provider.ValueSourc
+ """.trimIndent()
+
+ wrappingRuleAssertThat(code).hasNoLintViolations()
+ }
+
+ @Test
+ fun `fail if Junit4 is mixed with Junit5`() {
+ //language=kotlin
+ val code =
+ """
+ import org.junit.Test
+ import org.junit.jupiter.api.Test
+ """.trimIndent()
+
+ wrappingRuleAssertThat(code)
+ .hasLintViolationWithoutAutoCorrect(
+ 1,
+ 1,
+ "org.junit.Test and org.junit.jupiter.api.Test packages are from different JUnit versions. Don't mix Junit4 with Junit5 in a single test.",
+ )
+ }
+
+ @Test
+ fun `fail if multiple Junit4 is mixed with Junit5`() {
+ //language=kotlin
+ val code =
+ """
+ import org.junit.Test
+ import org.junit.Before
+ import org.junit.jupiter.api.Test
+ import org.junit.jupiter.api.BeforeEach
+ """.trimIndent()
+
+ wrappingRuleAssertThat(code)
+ .hasLintViolationWithoutAutoCorrect(
+ 1,
+ 1,
+ "org.junit.Test,org.junit.Before and org.junit.jupiter.api.Test,org.junit.jupiter.api.BeforeEach packages are from different JUnit versions. Don't mix Junit4 with Junit5 in a single test.",
+ )
+ }
+}
diff --git a/custom-ktlint-rules/src/test/kotlin/com/appunite/loudius/rules/UseStriktAssertionLibraryTest.kt b/custom-ktlint-rules/src/test/kotlin/com/appunite/loudius/rules/UseStriktAssertionLibraryTest.kt
index ac678534b..eca4d7f13 100644
--- a/custom-ktlint-rules/src/test/kotlin/com/appunite/loudius/rules/UseStriktAssertionLibraryTest.kt
+++ b/custom-ktlint-rules/src/test/kotlin/com/appunite/loudius/rules/UseStriktAssertionLibraryTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2023 AppUnite S.A.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.appunite.loudius.rules
import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule
diff --git a/settings.gradle b/settings.gradle
index 7e7a41fb0..8d7232340 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -16,3 +16,4 @@ rootProject.name = "Loudius"
include ':app'
include ':custom-ktlint-rules'
include ':components'
+include ':app-shared-tests'