mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-10-04 18:33:49 +00:00
Move to Github
This commit is contained in:
parent
5de0718210
commit
331c102992
61
.github/workflows/test-dev.yml
vendored
Normal file
61
.github/workflows/test-dev.yml
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
name: Development tests
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
name: Tests without emulator
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 11
|
||||
cache: 'gradle'
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
- name: Run tests
|
||||
run: ./gradlew app:check
|
||||
- name: Archive results
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
app/build/outputs/lint*
|
||||
app/build/reports
|
||||
|
||||
test_on_emulator:
|
||||
name: Tests with emulator
|
||||
runs-on: privileged
|
||||
container:
|
||||
image: ghcr.io/bitfireat/docker-android-ci:main
|
||||
options: --privileged
|
||||
env:
|
||||
ANDROID_HOME: /sdk
|
||||
ANDROID_AVD_HOME: /root/.android/avd
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
- name: Cache gradle dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
key: ${{ runner.os }}-1
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
|
||||
- name: Start emulator
|
||||
run: start-emulator.sh
|
||||
- name: Run connected tests
|
||||
run: ./gradlew app:connectedCheck
|
||||
- name: Archive results
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
app/build/reports
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
image: ghcr.io/bitfireat/docker-android-ci:main
|
||||
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
before_script:
|
||||
- export GRADLE_USER_HOME=`pwd`/.gradle; chmod +x gradlew
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .gradle/
|
||||
|
||||
test:
|
||||
stage: test
|
||||
tags:
|
||||
- privileged
|
||||
before_script:
|
||||
- curl -d "email=gitlab%40bitfire.at&password=$DAVTEST_TOKEN&action=Request+access" -X POST "https://davtest.dev001.net/access/"
|
||||
script:
|
||||
- start-emulator.sh
|
||||
- ./gradlew app:check app:connectedCheck
|
||||
artifacts:
|
||||
paths:
|
||||
- app/build/outputs/lint-results-debug.html
|
||||
- app/build/reports
|
||||
- build/reports
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
script:
|
||||
- ./gradlew app:dokka
|
||||
- mkdir public && mv app/build/dokka public
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
only:
|
||||
- master-ose
|
27
README.md
27
README.md
|
@ -1,5 +1,11 @@
|
|||
|
||||
[![Development tests](https://github.com/bitfireAT/davx5-ose/actions/workflows/test-dev.yml/badge.svg)](https://github.com/bitfireAT/davx5-ose/actions/workflows/test-dev.yml)
|
||||
[![License](https://img.shields.io/github/license/bitfireAT/davx5-ose)](https://github.com/bitfireAT/davx5-ose/blob/main/LICENSE)
|
||||
[![F-Droid](https://img.shields.io/f-droid/v/at.bitfire.davdroid)](https://f-droid.org/packages/at.bitfire.davdroid/)
|
||||
|
||||
![DAVx⁵ logo](app/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
|
||||
|
||||
|
||||
DAVx⁵
|
||||
========
|
||||
|
||||
|
@ -10,27 +16,24 @@ DAVx⁵ is licensed under the [GPLv3 License](LICENSE).
|
|||
|
||||
News and updates: [@davx5app](https://twitter.com/davx5app) on Twitter
|
||||
|
||||
Help, discussion, feature requests, bug reports and "issues": [DAVx⁵ forums](https://www.davx5.com/forums)
|
||||
|
||||
**If you want to support DAVx⁵, please consider [donating to DAVx⁵](https://www.davx5.com/donate)
|
||||
or [purchasing it](https://www.davx5.com/download).**
|
||||
|
||||
Generated KDoc: https://bitfireAT.gitlab.io/davx5-ose/dokka/app/
|
||||
**Help, discussion, feature requests, bug reports: [DAVx⁵ forums](https://www.davx5.com/forums)**
|
||||
|
||||
Parts of DAVx⁵ have been outsourced into these libraries:
|
||||
|
||||
* [cert4android](https://gitlab.com/bitfireAT/cert4android) – custom certificate management
|
||||
* [dav4jvm](https://gitlab.com/bitfireAT/dav4jvm) – WebDAV/CalDav/CardDAV framework
|
||||
* [ical4android](https://gitlab.com/bitfireAT/ical4android) – iCalendar processing and Calendar Provider access
|
||||
* [vcard4android](https://gitlab.com/bitfireAT/vcard4android) – vCard processing and Contacts Provider access
|
||||
* [cert4android](https://hublab.com/bitfireAT/cert4android) – custom certificate management
|
||||
* [dav4jvm](https://github.com/bitfireAT/dav4jvm) – WebDAV/CalDav/CardDAV framework
|
||||
* [ical4android](https://github.com/bitfireAT/ical4android) – iCalendar processing and Calendar Provider access
|
||||
* [vcard4android](https://github.com/bitfireAT/vcard4android) – vCard processing and Contacts Provider access
|
||||
|
||||
**If you want to support DAVx⁵, please consider [donating to DAVx⁵](https://www.davx5.com/donate)
|
||||
or [purchasing it](https://www.davx5.com/download).**
|
||||
|
||||
|
||||
USED THIRD-PARTY LIBRARIES
|
||||
==========================
|
||||
|
||||
Those libraries are used by DAVx⁵ (alphabetically):
|
||||
The most important libraries which are used by DAVx⁵ (alphabetically):
|
||||
|
||||
* [Color Picker](https://github.com/jaredrummler/ColorPicker) – [Apache License, Version 2.0](https://github.com/jaredrummler/ColorPicker/LICENSE)
|
||||
* [dnsjava](http://www.xbill.org/dnsjava/) – [BSD License](http://www.xbill.org/dnsjava/dnsjava-current/LICENSE)
|
||||
* [ez-vcard](https://github.com/mangstadt/ez-vcard) – [New BSD License](http://opensource.org/licenses/BSD-3-Clause)
|
||||
* [iCal4j](https://github.com/ical4j/ical4j) – [New BSD License](http://sourceforge.net/p/ical4j/ical4j/ci/default/tree/LICENSE)
|
||||
|
|
|
@ -1,204 +0,0 @@
|
|||
package at.bitfire.davdroid.ui
|
||||
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.test.espresso.*
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.typeText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.DrawerActions
|
||||
import androidx.test.espresso.contrib.DrawerMatchers.isClosed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.util.HumanReadables
|
||||
import androidx.test.espresso.util.TreeIterables
|
||||
import androidx.test.ext.junit.rules.activityScenarioRule
|
||||
import androidx.test.filters.LargeTest
|
||||
import at.bitfire.davdroid.R
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.Matchers
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
@LargeTest
|
||||
class AccountsActivityEspressoTest {
|
||||
|
||||
@get:Rule
|
||||
val activityScenarioRule = activityScenarioRule<AccountsActivity>()
|
||||
|
||||
private val username = "test"
|
||||
private val password = "test"
|
||||
private val baseUrl = "https://davtest.dev001.net/radicale/htpasswd/"
|
||||
|
||||
@Test
|
||||
fun accountsActivityTest() {
|
||||
skipIntroActivity()
|
||||
|
||||
onView(withId(R.id.fab)).perform(click())
|
||||
|
||||
// open first the option for Login with Base URL and then enter the test-data and confirm
|
||||
onView(withText(R.string.login_type_url)).perform(click())
|
||||
onView(withId(R.id.loginUrlBaseUrlEdittext)).perform(typeText(baseUrl), ViewActions.closeSoftKeyboard())
|
||||
onView(withId(R.id.loginUrlUsernameEdittext)).perform(typeText(username), ViewActions.closeSoftKeyboard())
|
||||
onView(withId(R.id.loginUrlPasswordEdittext)).perform(typeText(password), ViewActions.closeSoftKeyboard())
|
||||
onView(withId(R.id.login)).perform(click())
|
||||
|
||||
// The detect configuration screen (detect_configuration.xml) is not asserted here, it's just skipped.
|
||||
// login_account_details.xml is the next expected fragment, check if the expected headline appears and then click on the "Create Account" button
|
||||
onView(isRoot()).perform(waitForView(R.id.accountName, 30000))
|
||||
|
||||
onView(withText(R.string.login_account_name_info)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.create_account)).perform(click())
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
// check if the "test" calendar appeared
|
||||
onView(isRoot()).perform(waitForView(R.id.title, 5000))
|
||||
//onView(withId(R.id.title)).perform(waitForText("Test Addressbook", 30000))
|
||||
onView(withText("Test Addressbook")).check(matches(withText("Test Addressbook")))
|
||||
|
||||
// Go back to the overview with all Accounts
|
||||
Espresso.pressBack()
|
||||
//Thread.sleep(2000)
|
||||
|
||||
// check if the account exists and click on it
|
||||
onView(isRoot()).perform(waitForView((R.id.account_name), 5000))
|
||||
onView(withText("test")).check(matches(withText("test")))
|
||||
onView(withText("test")).perform(click())
|
||||
|
||||
// open the overflowMenu to delete the account
|
||||
val overflowMenuButton = onView(
|
||||
Matchers.allOf(withContentDescription("More options"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(R.id.toolbar),
|
||||
2),
|
||||
1),
|
||||
isDisplayed()))
|
||||
overflowMenuButton.perform(click())
|
||||
|
||||
// click on the delete button
|
||||
onView(withText(R.string.account_delete)).perform(click())
|
||||
onView(withText(R.string.account_delete_confirmation_title)).check(matches(isDisplayed()))
|
||||
// confirm deletion by clicking on YES
|
||||
onView(withId(android.R.id.button1)).perform(click())
|
||||
|
||||
// doublecheck to make sure that the account doesn't exist anymore. The welcome text is displayed
|
||||
onView(withText(R.string.account_list_empty)).check(matches(withText(R.string.account_list_empty)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun menuDrawerTest() {
|
||||
skipIntroActivity()
|
||||
|
||||
// TESTING ABOUT DIALOG
|
||||
// Open Drawer to click on navigation.
|
||||
onView(withId(R.id.drawer_layout))
|
||||
.check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
|
||||
.perform(DrawerActions.open()) // Open Drawer
|
||||
// check if about can be opened
|
||||
onView(withText(R.string.navigation_drawer_about)).perform(click())
|
||||
onView(withText(R.string.about_copyright)).check(matches(isDisplayed()))
|
||||
Espresso.pressBack()
|
||||
|
||||
// TESTING SETTINGS DIALOG
|
||||
// Open Drawer to click on navigation.
|
||||
onView(withId(R.id.drawer_layout))
|
||||
.check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
|
||||
.perform(DrawerActions.open()) // Open Drawer
|
||||
// check if about can be opened
|
||||
onView(withText(R.string.navigation_drawer_settings)).perform(click())
|
||||
onView(withText(R.string.app_settings_show_debug_info)).check(matches(isDisplayed()))
|
||||
Espresso.pressBack()
|
||||
|
||||
// TESTING WEBSITE MENU ENTRY
|
||||
// Open Drawer to click on navigation.
|
||||
onView(withId(R.id.drawer_layout))
|
||||
.check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
|
||||
.perform(DrawerActions.open()) // Open Drawer
|
||||
// check if Website can be opened
|
||||
//onView(withText(R.string.navigation_drawer_website)).perform(click())
|
||||
}
|
||||
|
||||
|
||||
private fun childAtPosition(
|
||||
parentMatcher: Matcher<View>, position: Int): Matcher<View> {
|
||||
|
||||
return object : TypeSafeMatcher<View>() {
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("Child at position $position in parent ")
|
||||
parentMatcher.describeTo(description)
|
||||
}
|
||||
|
||||
public override fun matchesSafely(view: View): Boolean {
|
||||
val parent = view.parent
|
||||
return parent is ViewGroup && parentMatcher.matches(parent)
|
||||
&& view == parent.getChildAt(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun skipIntroActivity() {
|
||||
try {
|
||||
onView(withId(R.id.takeControl)).check(matches(withText(R.string.intro_slogan2))) // intro_welcome is the first fragment, check first if the String Resource "intro_slogan2" is shown.
|
||||
// click through up to 5 intro fragments
|
||||
for (i in 1..5)
|
||||
try {
|
||||
onView(withId(R.id.next)).perform(click())
|
||||
} catch (ignored: Exception) { }
|
||||
onView(withId(R.id.done)).perform(click())
|
||||
} catch (ignored: NoMatchingViewException) {
|
||||
// the IntroActivity or some fragments of it may not show up every time
|
||||
}
|
||||
onView(withText(R.string.account_list_empty)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
/**
|
||||
* This ViewAction tells espresso to wait till a certain view is found in the view hierarchy.
|
||||
* Source: https://www.repeato.app/espresso-wait-for-view/
|
||||
* @param viewId The id of the view to wait for.
|
||||
* @param timeout The maximum time which espresso will wait for the view to show up (in milliseconds)
|
||||
*/
|
||||
private fun waitForView(viewId: Int, timeout: Long): ViewAction {
|
||||
return object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return isRoot()
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "wait for a specific view with id $viewId; during $timeout millis."
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, rootView: View) {
|
||||
uiController.loopMainThreadUntilIdle()
|
||||
val startTime = System.currentTimeMillis()
|
||||
val endTime = startTime + timeout
|
||||
val viewMatcher = withId(viewId)
|
||||
|
||||
do {
|
||||
// Iterate through all views on the screen and see if the view we are looking for is there already
|
||||
for (child in TreeIterables.breadthFirstViewTraversal(rootView)) {
|
||||
// found view with required ID
|
||||
if (viewMatcher.matches(child)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Loops the main thread for a specified period of time.
|
||||
// Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again.
|
||||
uiController.loopMainThreadForAtLeast(100)
|
||||
} while (System.currentTimeMillis() < endTime) // in case of a timeout we throw an exception -> test fails
|
||||
throw PerformException.Builder()
|
||||
.withCause(TimeoutException())
|
||||
.withActionDescription(this.description)
|
||||
.withViewDescription(HumanReadables.describe(rootView))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue