Add some more tests

This commit is contained in:
Phillip Thelen 2021-09-22 10:36:05 +02:00
parent 7cc7c6f87e
commit 9244cb0430
49 changed files with 573 additions and 776 deletions

View file

@ -6,6 +6,7 @@ apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'realm-android'
apply plugin: 'androidx.navigation.safeargs'
apply plugin: 'com.google.firebase.firebase-perf'
apply plugin: 'jacoco'
buildscript {
repositories {
@ -90,6 +91,7 @@ dependencies {
testImplementation "io.mockk:mockk:1.12.0"
testImplementation "io.mockk:mockk-android:1.12.0"
testImplementation 'io.kotest:kotest-assertions-core:4.6.2'
testImplementation 'io.kotest:kotest-framework-datatest:4.6.2'
//Leak Detection
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
@ -111,8 +113,8 @@ dependencies {
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation "androidx.paging:paging-runtime-ktx:3.0.1"
implementation 'com.plattysoft.leonids:LeonidsLib:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
implementation 'com.willowtreeapps:signinwithapplebutton:0.3'
@ -262,6 +264,7 @@ android {
android.testOptions {
unitTests.all {
useJUnitPlatform()
unitTests.returnDefaultValues = true
}
}
@ -337,6 +340,38 @@ gradle.projectsEvaluated {
apply plugin: 'com.google.gms.google-services'
jacoco {
toolVersion = "0.8.7"
}
// packages to exclude for example generated classes, R class and models package, add all packages that you wish to exclude from test coverage
def fileFilter = [
'**/*$ViewInjector*.*','**/*$ViewBinder*.*', '**/HabiticaIcons*.*', '**/DeviceName.*', '**/databinding/*Binding.*',
'**/R.class', '**/R.styleable', '**/R$*.class', '**/BuildConfig.*', '**/EmojiMap.*',
'**/Manifest*.*', 'android/**/*.*', '**/*RealmProxy*.*', '**/io/realm/*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/asm_instrumented_project_classes/prodDebug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
// override jacocTestReport task
task jacocoTestReport(type: JacocoReport, dependsOn: 'testProdDebugUnitTest') {
group = "Reporting"
reports {
html.enabled = true
xml.enabled = true
//html.destination = "${buildDir}/reports/jacoco"
}
sourceDirectories.from(files([mainSrc]))
classDirectories.from(files([debugTree]))
executionData.from(files("${buildDir}/jacoco/testProdDebugUnitTest.exec"))
afterEvaluate {
classDirectories.from(files(classDirectories.files.collect {
fileTree(dir: it, exclude: fileFilter)
}))
}
}
task ktlint(type: JavaExec, group: "verification") {
description = "Check Kotlin code style."
classpath = configurations.ktlint

View file

@ -19,7 +19,7 @@ interface TaskRepository : BaseRepository {
fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Flowable<TaskList>
fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable<TaskList>
fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)?): Flowable<TaskScoringResult?>
fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)?): Flowable<TaskScoringResult>
fun taskChecked(user: User?, taskId: String, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)?): Maybe<TaskScoringResult?>
fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task>

View file

@ -52,7 +52,7 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli
}
@Suppress("ReturnCount")
override fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)?): Flowable<TaskScoringResult?> {
override fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)?): Flowable<TaskScoringResult> {
val localData = if (user != null && appConfigManager.enableLocalTaskScoring()) {
ScoreTaskLocallyInteractor.score(user, task, if (up) TaskDirection.UP else TaskDirection.DOWN)
} else null

View file

@ -12,9 +12,7 @@ interface BaseLocalRepository {
fun close()
fun executeTransaction(transaction: (Realm) -> Unit)
fun executeTransaction(transaction: Realm.Transaction)
fun executeTransactionAsync(transaction: (Realm) -> Unit)
fun executeTransactionAsync(transaction: Realm.Transaction)
fun <T : BaseMainObject> modify(obj: T, transaction: (T) -> Unit)
fun <T : BaseMainObject> modifyWithRealm(obj: T, transaction: (Realm, T) -> Unit)
fun <T : BaseObject> getLiveObject(obj: T): T?

View file

@ -25,11 +25,6 @@ abstract class RealmBaseLocalRepository internal constructor(override var realm:
}
}
override fun executeTransaction(transaction: Realm.Transaction) {
if (isClosed) { return }
realm.executeTransaction(transaction)
}
override fun executeTransactionAsync(transaction: (Realm) -> Unit) {
if (isClosed) { return }
realm.executeTransactionAsync {
@ -37,11 +32,6 @@ abstract class RealmBaseLocalRepository internal constructor(override var realm:
}
}
override fun executeTransactionAsync(transaction: Realm.Transaction) {
if (isClosed) { return }
realm.executeTransactionAsync(transaction)
}
override fun <T : BaseObject> getUnmanagedCopy(managedObject: T): T {
return if (managedObject is RealmObject && managedObject.isManaged && managedObject.isValid) {
realm.copyFromRealm(managedObject)

View file

@ -211,11 +211,9 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
}
} else {
liveMessage?.likes?.filter { userId == it.id }?.forEach { like ->
executeTransaction(
Realm.Transaction {
like.deleteFromRealm()
}
)
executeTransaction {
like.deleteFromRealm()
}
}
executeTransaction {
liveMessage?.likeCount = liveMessage?.likes?.size ?: 0

View file

@ -79,7 +79,7 @@ constructor(
displayType = SnackbarDisplayType.FAILURE
}
}
if (mp != null && mp > 0 && user?.hasClass() == true) {
if (mp != null && mp > 0 && user?.hasClass == true) {
container.addView(createTextView(context, mp, HabiticaIconsHelper.imageOfMagic()))
}
if (questDamage != null && questDamage > 0) {

View file

@ -12,10 +12,11 @@ interface Avatar {
val sleep: Boolean
val stats: Stats?
val preferences: AvatarPreferences?
val flags: AvatarFlags?
val gemCount: Int
val hourglassCount: Int
val costume: Outfit?
val equipped: Outfit?
fun hasClass(): Boolean
val hasClass: Boolean
fun isValid(): Boolean
}

View file

@ -0,0 +1,5 @@
package com.habitrpg.android.habitica.models
interface AvatarFlags {
var classSelected: Boolean
}

View file

@ -19,7 +19,7 @@ open class QuestBoss : RealmObject(), BaseObject {
var rage: QuestBossRage? = null
val hasRage: Boolean
get() {
return rage?.value ?: 0.0 > 0.0
}
get() {
return rage?.value ?: 0.0 > 0.0
}
}

View file

@ -16,6 +16,7 @@ open class Member : RealmObject(), Avatar, BaseObject {
override var stats: Stats? = null
var inbox: Inbox? = null
override var preferences: MemberPreferences? = null
override var flags: MemberFlags? = null
override val gemCount: Int
get() = 0
override val hourglassCount: Int
@ -50,9 +51,10 @@ open class Member : RealmObject(), Avatar, BaseObject {
val formattedUsername: String?
get() = if (username != null) "@$username" else null
override fun hasClass(): Boolean {
return preferences?.disableClasses == false && stats?.habitClass?.isNotEmpty() == true
}
override val hasClass: Boolean
get() {
return preferences?.disableClasses == false && stats?.habitClass?.isNotEmpty() == true
}
override val sleep: Boolean
get() = preferences?.sleep ?: false

View file

@ -0,0 +1,10 @@
package com.habitrpg.android.habitica.models.members
import com.habitrpg.android.habitica.models.AvatarFlags
import io.realm.RealmObject
import io.realm.annotations.RealmClass
@RealmClass(embedded = true)
open class MemberFlags : RealmObject(), AvatarFlags {
override var classSelected: Boolean = false
}

View file

@ -1,6 +1,7 @@
package com.habitrpg.android.habitica.models.social
import com.habitrpg.android.habitica.models.Avatar
import com.habitrpg.android.habitica.models.AvatarFlags
import com.habitrpg.android.habitica.models.user.Items
import com.habitrpg.android.habitica.models.user.Outfit
import com.habitrpg.android.habitica.models.user.Preferences
@ -30,11 +31,14 @@ open class UserStyles : RealmObject(), Avatar {
override val equipped: Outfit?
get() = items?.gear?.equipped
override fun hasClass(): Boolean {
return false
}
override val hasClass: Boolean
get() {
return false
}
override var stats: Stats? = null
override var preferences: Preferences? = null
override val flags: AvatarFlags?
get() = null
private var items: Items? = null
}

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.models.user
import com.habitrpg.android.habitica.models.AvatarFlags
import com.habitrpg.android.habitica.models.BaseObject
import com.habitrpg.android.habitica.models.TutorialStep
import io.realm.RealmList
@ -7,14 +8,14 @@ import io.realm.RealmObject
import io.realm.annotations.RealmClass
@RealmClass(embedded = true)
open class Flags : RealmObject(), BaseObject {
open class Flags : RealmObject(), BaseObject, AvatarFlags {
var tutorial: RealmList<TutorialStep>? = null
var showTour = false
var dropsEnabled = false
var itemsEnabled = false
var newStuff = false
var lastNewStuffRead: String? = null
var classSelected = false
override var classSelected = false
var rebirthEnabled = false
var welcomed = false
var armoireEnabled = false

View file

@ -13,7 +13,8 @@ open class SpecialItems : RealmObject(), BaseObject {
var snowball: Int = 0
var spookySparkles: Int = 0
fun hasSpecialItems(): Boolean {
return seafoam > 0 || shinySeed > 0 || snowball > 0 || spookySparkles > 0
}
val hasSpecialItems: Boolean
get() {
return seafoam > 0 || shinySeed > 0 || snowball > 0 || spookySparkles > 0
}
}

View file

@ -34,15 +34,17 @@ open class SubscriptionPlan : RealmObject(), BaseObject {
return customerId != null && (dateTerminated == null || dateTerminated!!.after(today))
}
fun totalNumberOfGems(): Int {
return if (customerId == null || consecutive == null) {
0
} else 25 + consecutive!!.gemCapExtra
}
val totalNumberOfGems: Int
get() {
return if (isActive) {
25 + (consecutive?.gemCapExtra ?: 0)
} else 0
}
fun numberOfGemsLeft(): Int {
return totalNumberOfGems() - gemsBought!!
}
val numberOfGemsLeft: Int
get() {
return totalNumberOfGems - (gemsBought ?: 0)
}
companion object {
var PLANID_BASIC = "basic"

View file

@ -42,7 +42,7 @@ open class User : RealmObject(), BaseMainObject, Avatar, VersionedObject {
var items: Items? = null
@SerializedName("auth")
var authentication: Authentication? = null
var flags: Flags? = null
override var flags: Flags? = null
var contributor: ContributorInfo? = null
var backer: Backer? = null
var invitations: Invitations? = null
@ -98,9 +98,10 @@ open class User : RealmObject(), BaseMainObject, Avatar, VersionedObject {
override val equipped: Outfit?
get() = items?.gear?.equipped
override fun hasClass(): Boolean {
return preferences?.disableClasses != true && flags?.classSelected == true && stats?.habitClass?.isNotEmpty() == true
}
override val hasClass: Boolean
get() {
return preferences?.disableClasses != true && flags?.classSelected == true && stats?.habitClass?.isNotEmpty() == true
}
override val currentMount: String?
get() = items?.currentMount ?: ""
@ -110,20 +111,14 @@ open class User : RealmObject(), BaseMainObject, Avatar, VersionedObject {
override val sleep: Boolean
get() = preferences?.sleep ?: false
fun hasParty(): Boolean {
return this.party?.id?.length ?: 0 > 0
}
val hasParty: Boolean
get() {
return this.party?.id?.length ?: 0 > 0
}
val isSubscribed: Boolean
get() {
val plan = purchased?.plan
var isSubscribed = false
if (plan != null) {
if (plan.isActive) {
isSubscribed = true
}
}
return isSubscribed
return purchased?.plan?.isActive == true
}
val onboardingAchievements: List<UserAchievement>

View file

@ -5,7 +5,12 @@ import io.realm.RealmObject
import io.realm.annotations.RealmClass
@RealmClass(embedded = true)
open class UserAchievement : RealmObject(), BaseObject {
open class UserAchievement() : RealmObject(), BaseObject {
var key: String? = null
var earned: Boolean = false
constructor(key: String, earned: Boolean) : this() {
this.key = key
this.earned = earned
}
}

View file

@ -59,7 +59,7 @@ class AvatarWithBarsViewModel(private val context: Context, private val binding:
binding.mpBar.visibility = if (stats.habitClass == null || stats.lvl ?: 0 < 10 || user.preferences?.disableClasses == true) View.GONE else View.VISIBLE
if (!user.hasClass()) {
if (!user.hasClass) {
setUserLevel(context, binding.lvlTv, stats.lvl)
} else {
setUserLevelWithClass(

View file

@ -248,7 +248,7 @@ class NavigationDrawerFragment : DialogFragment() {
.distinctUntilChanged { firstTeams, secondTeams -> firstTeams == secondTeams }
.subscribe(
{
getItemWithIdentifier(SIDEBAR_TEAMS)?.isVisible = it.size != 0
getItemWithIdentifier(SIDEBAR_TEAMS)?.isVisible = it.isNotEmpty()
adapter.setTeams(it)
},
RxErrorHandler.handleEmptyError()
@ -281,7 +281,6 @@ class NavigationDrawerFragment : DialogFragment() {
market.pillText = null
market.subtitle = null
}
adapter.notifyDataSetChanged()
val shop = getItemWithIdentifier(SIDEBAR_SHOPS_SEASONAL) ?: return
shop.pillText = context?.getString(R.string.open)
@ -313,11 +312,11 @@ class NavigationDrawerFragment : DialogFragment() {
val specialItems = user.items?.special
var hasSpecialItems = false
if (specialItems != null) {
hasSpecialItems = specialItems.hasSpecialItems()
hasSpecialItems = specialItems.hasSpecialItems
}
val item = getItemWithIdentifier(SIDEBAR_SKILLS)
if (item != null) {
if (!user.hasClass() && !hasSpecialItems) {
if (!user.hasClass && !hasSpecialItems) {
item.isVisible = false
} else {
if (user.stats?.lvl ?: 0 < HabiticaSnackbar.MIN_LEVEL_FOR_SKILLS && (!hasSpecialItems)) {
@ -378,10 +377,10 @@ class NavigationDrawerFragment : DialogFragment() {
}
val partyMenuItem = getItemWithIdentifier(SIDEBAR_PARTY)
if (user.hasParty() && partyMenuItem?.bundle == null) {
if (user.hasParty && partyMenuItem?.bundle == null) {
partyMenuItem?.transitionId = R.id.partyFragment
partyMenuItem?.bundle = bundleOf(Pair("partyID", user.party?.id))
} else if (!user.hasParty()) {
} else if (!user.hasParty) {
partyMenuItem?.transitionId = R.id.noPartyFragment
partyMenuItem?.bundle = null
}

View file

@ -171,7 +171,7 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
specialCategory.text = getString(R.string.special)
if (user?.isValid == true && user.purchased?.plan?.isActive == true) {
val item = ShopItem.makeGemItem(context?.resources)
item.limitedNumberLeft = user.purchased?.plan?.numberOfGemsLeft()
item.limitedNumberLeft = user.purchased?.plan?.numberOfGemsLeft
specialCategory.items.add(item)
}
specialCategory.items.add(ShopItem.makeFortifyItem(context?.resources))

View file

@ -176,7 +176,7 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
private fun refresh() {
compositeSubscription.add(
userRepository.retrieveUser(false, forced = true)
.filter { it.hasParty() }
.filter { it.hasParty }
.flatMap { socialRepository.retrieveGroup("party") }
.flatMap { group1 -> socialRepository.retrieveGroupMembers(group1.id, true) }
.doOnComplete { binding?.refreshLayout?.isRefreshing = false }

View file

@ -70,7 +70,7 @@ class GroupMemberViewHolder(itemView: View) : androidx.recyclerview.widget.Recyc
binding.displayNameTextview.username = user.profile?.name
binding.displayNameTextview.tier = user.contributor?.level ?: 0
if (user.hasClass()) {
if (user.hasClass) {
binding.sublineTextview.text = itemView.context.getString(R.string.user_level_with_class, user.stats?.lvl, user.stats?.getTranslatedClassName(itemView.context))
} else {
binding.sublineTextview.text = itemView.context.getString(R.string.user_level, user.stats?.lvl)

View file

@ -255,8 +255,8 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
currencyView.hourglasses = user.hourglassCount.toDouble()
if ("gems" == shopItem.purchaseType) {
val maxGems = user.purchased?.plan?.totalNumberOfGems() ?: 0
val gemsLeft = user.purchased?.plan?.numberOfGemsLeft()
val maxGems = user.purchased?.plan?.totalNumberOfGems ?: 0
val gemsLeft = user.purchased?.plan?.numberOfGemsLeft
if (maxGems > 0) {
limitedTextView.text = context.getString(R.string.gems_left_max, gemsLeft, maxGems)
} else {
@ -269,7 +269,7 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
limitedTextView.setBackgroundColor(ContextCompat.getColor(context, R.color.green_10))
}
val gemContent = additionalContentView as? PurchaseDialogGemsContent
gemContent?.binding?.stepperView?.maxValue = (user.purchased?.plan?.numberOfGemsLeft() ?: 1).toDouble()
gemContent?.binding?.stepperView?.maxValue = (user.purchased?.plan?.numberOfGemsLeft ?: 1).toDouble()
}
buyButton.elevation = 0f

View file

@ -28,7 +28,7 @@ class PurchaseDialogQuestContent(context: Context) : PurchaseDialogContent(conte
binding.questTypeTextView.setText(R.string.boss_quest)
binding.questCollectView.visibility = View.GONE
binding.bossHealthText.text = questContent.boss?.hp.toString()
if (questContent.boss?.hasRage() == true) {
if (questContent.boss?.hasRage == true) {
binding.rageMeterView.visibility = View.VISIBLE
}
binding.questDifficultyView.rating = questContent.boss?.str ?: 1f

View file

@ -76,7 +76,7 @@ class OldQuestProgressView : LinearLayout {
if (progress != null) {
binding.bossHealthView.set(progress.hp, quest.boss?.hp?.toDouble() ?: 0.0)
}
if (quest.boss?.hasRage() == true) {
if (quest.boss?.hasRage == true) {
binding.bossRageView.visibility = View.VISIBLE
binding.bossRageView.set(progress?.rage ?: 0.0, quest.boss?.rage?.value ?: 0.0)
} else {

View file

@ -119,7 +119,7 @@ class QuestProgressView : LinearLayout {
binding.bossHealthView.set(progress.progress?.hp ?: 0.0, quest.boss?.hp?.toDouble() ?: 0.0)
binding.collectedItemsNumberView.visibility = View.GONE
if (quest.boss?.hasRage() == true) {
if (quest.boss?.hasRage == true) {
binding.rageMeterView.visibility = View.VISIBLE
binding.bossRageView.visibility = View.VISIBLE
binding.rageMeterView.text = quest.boss?.rage?.title

View file

@ -96,7 +96,7 @@ class SubscriptionDetailsView : LinearLayout {
} else {
binding.monthsSubscribedTextView.text = resources.getString(R.string.x_months, plan.consecutive?.count ?: 0)
}
binding.gemCapTextView.text = plan.totalNumberOfGems().toString()
binding.gemCapTextView.text = plan.totalNumberOfGems.toString()
binding.currentHourglassesTextView.text = plan.consecutive?.trinkets.toString()
binding.changeSubscriptionButton.visibility = View.VISIBLE

View file

@ -5,7 +5,7 @@ import io.kotest.core.spec.style.AnnotationSpec
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
open class BaseAnnotationTestCase: AnnotationSpec() {
open class BaseAnnotationTestCase : AnnotationSpec() {
@MockK
lateinit var mockContext: Context

View file

@ -0,0 +1,170 @@
package com.habitrpg.android.habitica.data.implementation
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.local.TaskLocalRepository
import com.habitrpg.android.habitica.models.BaseObject
import com.habitrpg.android.habitica.models.responses.TaskDirectionData
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.tasks.TasksOrder
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import io.kotest.common.ExperimentalKotest
import io.kotest.core.spec.style.WordSpec
import io.kotest.framework.concurrency.eventually
import io.kotest.matchers.shouldBe
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.subscribers.TestSubscriber
import io.realm.Realm
import java.util.UUID
@OptIn(ExperimentalKotest::class)
class TaskRepositoryImplTest : WordSpec({
lateinit var repository: TaskRepository
val localRepository = mockk<TaskLocalRepository>()
val apiClient = mockk<ApiClient>()
beforeEach {
val slot = slot<((Realm) -> Unit)>()
every { localRepository.executeTransaction(transaction = capture(slot)) } answers {
slot.captured(mockk(relaxed = true))
}
repository = TaskRepositoryImpl(
localRepository,
apiClient,
"",
mockk(relaxed = true),
mockk(relaxed = true)
)
val liveObjectSlot = slot<BaseObject>()
every { localRepository.getLiveObject(capture(liveObjectSlot)) } answers {
liveObjectSlot.captured
}
}
"retrieveTasks" should {
"save tasks locally" {
val list = TaskList()
every { apiClient.tasks } returns Flowable.just(list)
every { localRepository.saveTasks("", any(), any()) } returns Unit
val order = TasksOrder()
val subscriber = TestSubscriber<TaskList>()
repository.retrieveTasks("", order).subscribe(subscriber)
subscriber.assertComplete()
verify { localRepository.saveTasks("", order, list) }
}
}
"taskChecked" should {
val task = Task()
task.id = UUID.randomUUID().toString()
lateinit var user: User
beforeEach {
user = spyk(User())
user.stats = Stats()
}
"debounce" {
every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(TaskDirectionData())
repository.taskChecked(user, task, true, false, null).subscribe()
repository.taskChecked(user, task, true, false, null).subscribe()
verify(exactly = 1) { apiClient.postTaskDirection(any(), any()) }
}
"get user if not passed" {
every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(TaskDirectionData())
every { localRepository.getUser("") } returns Flowable.just(user)
repository.taskChecked(null, task, true, false, null)
eventually(5000) {
localRepository.getUser("")
}
}
"does not update user for team tasks" {
val data = TaskDirectionData()
data.lvl = 0
every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data)
val subscriber = TestSubscriber<TaskScoringResult>()
repository.taskChecked(user, task, true, false, null).subscribe(subscriber)
subscriber.assertComplete()
verify(exactly = 0) { user.stats }
subscriber.values().first().level shouldBe null
}
"builds task result correctly" {
val data = TaskDirectionData()
data.lvl = 10
data.hp = 20.0
data.mp = 30.0
data.gp = 40.0
user.stats?.lvl = 10
user.stats?.hp = 8.0
user.stats?.mp = 4.0
every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data)
val subscriber = TestSubscriber<TaskScoringResult>()
repository.taskChecked(user, task, true, false, null).subscribe(subscriber)
subscriber.assertComplete()
subscriber.values().first().level shouldBe 10
subscriber.values().first().healthDelta shouldBe 12.0
subscriber.values().first().manaDelta shouldBe 26.0
subscriber.values().first().hasLeveledUp shouldBe false
}
"set hasLeveledUp correctly" {
val subscriber = TestSubscriber<TaskScoringResult>()
val data = TaskDirectionData()
data.lvl = 11
user.stats?.lvl = 10
every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data)
repository.taskChecked(user, task, true, false, null).subscribe(subscriber)
subscriber.assertComplete()
subscriber.values().first().level shouldBe 11
subscriber.values().first().hasLeveledUp shouldBe true
}
"handle stats not being there" {
val subscriber = TestSubscriber<TaskScoringResult>()
val data = TaskDirectionData()
data.lvl = 1
user.stats = null
every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data)
repository.taskChecked(user, task, true, false, null).subscribe(subscriber)
subscriber.assertComplete()
}
"update daily streak" {
val subscriber = TestSubscriber<TaskScoringResult>()
val data = TaskDirectionData()
data.delta = 1.0f
data.lvl = 1
task.type = Task.TYPE_DAILY
task.value = 0.0
every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data)
repository.taskChecked(user, task, true, false, null).subscribe(subscriber)
subscriber.assertComplete()
task.streak shouldBe 1
task.completed shouldBe true
}
"update habit counter" {
val subscriber = TestSubscriber<TaskScoringResult>()
val data = TaskDirectionData()
data.delta = 1.0f
data.lvl = 1
task.type = Task.TYPE_HABIT
task.value = 0.0
every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data)
repository.taskChecked(user, task, true, false, null).subscribe(subscriber)
subscriber.assertComplete()
task.counterUp shouldBe 1
data.delta = -10.0f
every { apiClient.postTaskDirection(any(), "down") } returns Flowable.just(data)
val downSubscriber = TestSubscriber<TaskScoringResult>()
repository.taskChecked(user, task, false, true, null).subscribe(downSubscriber)
downSubscriber.assertComplete()
task.counterUp shouldBe 1
task.counterDown shouldBe 1
}
}
afterEach { clearAllMocks() }
})

View file

@ -1,7 +1,6 @@
package com.habitrpg.android.habitica.helpers
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.Locale

View file

@ -9,7 +9,7 @@ import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
class NumberAbbreviatorTest: StringSpec({
class NumberAbbreviatorTest : StringSpec({
val mockContext = mockk<Context>()
beforeEach {
every { mockContext.getString(R.string.thousand_abbrev) } returns "k"
@ -35,9 +35,14 @@ class NumberAbbreviatorTest: StringSpec({
abbreviate(mockContext, 1990000000.0, 2) shouldBe "1.99b"
}
"it abbreviates trillions" {
abbreviate(mockContext, 1990000000000.0, 2) shouldBe "1.99t"
}
"it abbreviates thousands without additional decimals" {
abbreviate(mockContext, 1000.0, 2) shouldBe "1k"
abbreviate(mockContext, 1500.0, 2) shouldBe "1.5k"
abbreviate(mockContext, 1500.0, 0) shouldBe "1k"
}
"it rounds correctly" {

View file

@ -8,10 +8,9 @@ import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.user.Stats
import io.kotest.matchers.shouldBe
import java.util.ArrayList
class UserStatComputerTest: BaseAnnotationTestCase() {
class UserStatComputerTest : BaseAnnotationTestCase() {
private val userStatComputer: UserStatComputer = UserStatComputer()
private val user: Member = Member()
private val equipment: Equipment
@ -46,6 +45,42 @@ class UserStatComputerTest: BaseAnnotationTestCase() {
attributeRow.summary shouldBe false
}
@Test
fun shouldReturnClassBonusForHealer() {
user.stats!!.habitClass = Stats.HEALER
equipment.klass = Stats.HEALER
val statsRows = userStatComputer.computeClassBonus(equipmentList, user)
val attributeRow = statsRows[2] as AttributeRow
(str * 0.0f).toDouble() shouldBe attributeRow.strVal.toDouble()
(intStat * 0.5f).toDouble() shouldBe attributeRow.intVal.toDouble()
(con * 0.5f).toDouble() shouldBe attributeRow.conVal.toDouble()
(per * 0.0f).toDouble() shouldBe attributeRow.perVal.toDouble()
}
@Test
fun shouldReturnClassBonusForWarrior() {
user.stats!!.habitClass = Stats.WARRIOR
equipment.klass = Stats.WARRIOR
val statsRows = userStatComputer.computeClassBonus(equipmentList, user)
val attributeRow = statsRows[2] as AttributeRow
(str * 0.5f).toDouble() shouldBe attributeRow.strVal.toDouble()
(intStat * 0.0f).toDouble() shouldBe attributeRow.intVal.toDouble()
(con * 0.5f).toDouble() shouldBe attributeRow.conVal.toDouble()
(per * 0.0f).toDouble() shouldBe attributeRow.perVal.toDouble()
}
@Test
fun shouldReturnClassBonusForMage() {
user.stats!!.habitClass = Stats.MAGE
equipment.klass = Stats.MAGE
val statsRows = userStatComputer.computeClassBonus(equipmentList, user)
val attributeRow = statsRows[2] as AttributeRow
(str * 0.0f).toDouble() shouldBe attributeRow.strVal.toDouble()
(intStat * 0.5f).toDouble() shouldBe attributeRow.intVal.toDouble()
(con * 0.0f).toDouble() shouldBe attributeRow.conVal.toDouble()
(per * 0.5f).toDouble() shouldBe attributeRow.perVal.toDouble()
}
@Test
fun ShouldReturnClassBonusRowWhenSpecialClassMatches() {
user.stats!!.habitClass = Stats.ROGUE

View file

@ -7,7 +7,7 @@ import io.kotest.matchers.shouldBe
import java.util.Calendar
import java.util.Date
class SubscriptionPlanTest: BaseAnnotationTestCase() {
class SubscriptionPlanTest : BaseAnnotationTestCase() {
private var plan: SubscriptionPlan? = null
@AnnotationSpec.BeforeEach
fun setUp() {

View file

@ -8,7 +8,7 @@ import com.habitrpg.android.habitica.models.user.User
import io.kotest.matchers.shouldBe
import io.realm.RealmList
class UserTest: BaseAnnotationTestCase() {
class UserTest : BaseAnnotationTestCase() {
private var user: User? = null
@BeforeEach
fun setup() {
@ -33,7 +33,7 @@ class UserTest: BaseAnnotationTestCase() {
@get:Test
val petsFoundCount_onNoPetCollectionAvailable_shouldReturnZero: Unit
get() {
user?.petsFoundCount shouldBe 0
user?.petsFoundCount shouldBe 0
}
@get:Test
@ -52,6 +52,6 @@ class UserTest: BaseAnnotationTestCase() {
@get:Test
val mountsTamedCount_onNoMountCollectionAvailable_shouldReturnZero: Unit
get() {
user?.mountsTamedCount shouldBe 0
user?.mountsTamedCount shouldBe 0
}
}

View file

@ -1,6 +1,5 @@
package com.habitrpg.android.habitica.models.inventory
import android.content.Context
import com.habitrpg.android.habitica.BaseAnnotationTestCase
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.getTranslatedType
@ -10,7 +9,7 @@ import io.mockk.every
private const val FAKE_STANDARD = "Standard"
private const val FAKE_PREMIUM = "premium"
class MountTest: BaseAnnotationTestCase() {
class MountTest : BaseAnnotationTestCase() {
private var mount: Mount = Mount()
@Test

View file

@ -1,6 +1,5 @@
package com.habitrpg.android.habitica.models.inventory
import android.content.Context
import com.habitrpg.android.habitica.BaseAnnotationTestCase
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.getTranslatedType
@ -10,7 +9,7 @@ import io.mockk.every
private const val FAKE_STANDARD = "Standard"
private const val FAKE_PREMIUM = "premium"
class PetTest: BaseAnnotationTestCase() {
class PetTest : BaseAnnotationTestCase() {
private var pet: Pet = Pet()
@Test

View file

@ -1,8 +1,40 @@
package com.habitrpg.android.habitica.models.members
import io.kotest.core.spec.style.StringSpec
import com.habitrpg.android.habitica.models.user.Stats
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.shouldBe
class MemberTest : StringSpec({
"hasClass" { }
class MemberTest : WordSpec({
val member = Member()
beforeEach {
member.preferences = MemberPreferences()
member.stats = Stats()
}
"hasClass" should {
"false if classes are disabled" {
member.preferences?.disableClasses = true
member.hasClass shouldBe false
}
"false if class is empty" {
member.hasClass shouldBe false
}
"false if no class was chosen" {
member.flags?.classSelected = false
member.hasClass shouldBe false
}
"false if class was selected but then disabled" {
member.flags?.classSelected = true
member.preferences?.disableClasses = true
member.hasClass shouldBe false
}
"true if class was selected and not disabled" {
member.flags?.classSelected = true
member.stats?.habitClass = Stats.ROGUE
member.hasClass shouldBe true
}
}
})

View file

@ -0,0 +1,37 @@
package com.habitrpg.android.habitica.models.user
import io.kotest.common.ExperimentalKotest
import io.kotest.core.spec.style.WordSpec
import io.kotest.datatest.withData
import io.kotest.matchers.shouldBe
@ExperimentalKotest
class SpecialItemsTest : WordSpec({
data class SpecialItemsData(val snowBalls: Int, val seafoam: Int, val shinyseed: Int, val spookySparkles: Int)
lateinit var items: SpecialItems
beforeEach {
items = SpecialItems()
}
"hasSpecialItems" should {
"false if none are owned" {
items.hasSpecialItems shouldBe false
}
"true if any are owned" {
items.snowball = 4
items.hasSpecialItems shouldBe true
}
withData(
SpecialItemsData(1, 0, 0, 0),
SpecialItemsData(0, 1, 0, 0),
SpecialItemsData(0, 0, 1, 0),
SpecialItemsData(0, 0, 0, 1),
SpecialItemsData(1, 3, 4, 8)
) { (snowballs, seafoam, shinyseeds, spookySparkles) ->
items.snowball = snowballs
items.seafoam = seafoam
items.shinySeed = shinyseeds
items.spookySparkles = spookySparkles
items.hasSpecialItems shouldBe true
}
}
})

View file

@ -0,0 +1,70 @@
package com.habitrpg.android.habitica.models.user
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.shouldBe
import java.util.Date
class SubscriptionPlanTest : WordSpec({
lateinit var plan: SubscriptionPlan
beforeEach {
plan = SubscriptionPlan()
}
"isActive" should {
"true if user has valid subscription" {
plan.customerId = "some-id"
plan.isActive shouldBe true
}
"true if user has cancelled subscription with remaining time" {
plan.customerId = "some-id"
plan.dateTerminated = Date(Date().time + 10000)
plan.isActive shouldBe true
}
"false if user has cancelled subscription without remaining time" {
plan.customerId = "some-id"
plan.dateTerminated = Date(Date().time - 10000)
plan.isActive shouldBe false
}
}
"totalNumberOfGems" should {
beforeEach {
plan.customerId = "some-id"
}
"0 without an active subscription" {
plan.customerId = null
plan.totalNumberOfGems shouldBe 0
}
"25 without extra consecutive bonus" {
plan.totalNumberOfGems shouldBe 25
}
"35 with extra consecutive bonus" {
plan.consecutive = SubscriptionPlanConsecutive()
plan.consecutive?.gemCapExtra = 15
plan.totalNumberOfGems shouldBe 40
}
}
"numberOfGemsLeft" should {
beforeEach {
plan.customerId = "some-id"
}
"0 without an active subscription" {
plan.customerId = null
plan.numberOfGemsLeft shouldBe 0
}
"according to already purchased amount" {
plan.gemsBought = 10
plan.numberOfGemsLeft shouldBe 15
}
"according to already purchased amount with bonus" {
plan.consecutive = SubscriptionPlanConsecutive()
plan.consecutive?.gemCapExtra = 10
plan.gemsBought = 10
plan.numberOfGemsLeft shouldBe 25
}
}
})

View file

@ -0,0 +1,82 @@
package com.habitrpg.android.habitica.models.user
import com.habitrpg.android.habitica.models.social.UserParty
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.shouldBe
import io.realm.RealmList
import java.util.UUID
class UserTest : WordSpec({
val user = User()
beforeEach {
user.preferences = Preferences()
user.stats = Stats()
user.flags = Flags()
user.party = UserParty()
user.purchased = Purchases()
user.purchased?.plan = SubscriptionPlan()
}
"hasClass" should {
"false if classes are disabled" {
user.preferences?.disableClasses = true
user.hasClass shouldBe false
}
"false if class is empty" {
user.hasClass shouldBe false
}
"false if no class was chosen" {
user.flags?.classSelected = false
user.hasClass shouldBe false
}
"false if class was selected but then disabled" {
user.flags?.classSelected = true
user.preferences?.disableClasses = true
user.hasClass shouldBe false
}
"true if class was selected and not disabled" {
user.flags?.classSelected = true
user.preferences?.disableClasses = false
user.stats?.habitClass = Stats.ROGUE
user.hasClass shouldBe true
}
}
"Onboarding Achievements" should {
beforeEach {
user.achievements = RealmList()
for (key in User.ONBOARDING_ACHIEVEMENT_KEYS) {
user.achievements.add(UserAchievement(key, false))
}
}
"hasCompletedOnboarding should be true if all onboarding achievements are completed" {
user.onboardingAchievements.forEach { it.earned = true }
user.hasCompletedOnboarding shouldBe true
}
"hasCompletedOnboarding should be false if not all onboarding achievements are completed" {
user.onboardingAchievements.get(2).earned = true
user.hasCompletedOnboarding shouldBe false
}
}
"hasParty" should {
"true if user has valid party" {
user.party?.id = UUID.randomUUID().toString()
user.hasParty shouldBe true
}
"false if user has no party data" {
user.party = null
user.hasParty shouldBe false
}
"false if user has invalid party" {
user.hasParty shouldBe false
}
}
})

View file

@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.utils
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.habitrpg.android.habitica.BaseAnnotationTestCase
@ -10,7 +9,7 @@ import io.kotest.matchers.shouldBe
import java.lang.reflect.Type
import java.util.Date
class DateDeserializerTest: BaseAnnotationTestCase() {
class DateDeserializerTest : BaseAnnotationTestCase() {
var deserializer = DateDeserializer()
lateinit var deserializationContext: JsonDeserializationContext
lateinit var serializationContext: JsonSerializationContext
@ -66,7 +65,8 @@ class DateDeserializerTest: BaseAnnotationTestCase() {
val dateElement: JsonElement = deserializer!!.serialize(
Date(
referenceTimestamp!!
), Date::class.java, serializationContext
),
Date::class.java, serializationContext
)
dateElement.asString shouldBe "2015-09-28T13:00:00.000Z"
}
@ -77,4 +77,4 @@ class DateDeserializerTest: BaseAnnotationTestCase() {
deserializer!!.serialize(null, Date::class.java, serializationContext)
dateElement.asString shouldBe ""
}
}
}

View file

@ -1,4 +0,0 @@
sdk=21
packageName=com.habitrpg.android.habitica
manifest=AndroidManifest.xml
application=com.habitrpg.android.habitica.TestApplication

View file

@ -49,7 +49,7 @@
<ID>ComplexMethod:SubscriptionDetailsView.kt$SubscriptionDetailsView$fun setPlan(plan: SubscriptionPlan)</ID>
<ID>ComplexMethod:TaskFormActivity.kt$TaskFormActivity$override fun onCreate(savedInstanceState: Bundle?)</ID>
<ID>ComplexMethod:TaskFormActivity.kt$TaskFormActivity$private fun fillForm(task: Task)</ID>
<ID>ComplexMethod:TaskRepositoryImpl.kt$TaskRepositoryImpl$@Suppress("ReturnCount") override fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -&gt; Unit)?): Flowable&lt;TaskScoringResult?&gt;</ID>
<ID>ComplexMethod:TaskRepositoryImpl.kt$TaskRepositoryImpl$@Suppress("ReturnCount") override fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -&gt; Unit)?): Flowable&lt;TaskScoringResult&gt;</ID>
<ID>ComplexMethod:TaskRepositoryImpl.kt$TaskRepositoryImpl$private fun handleTaskResponse(user: User, res: TaskDirectionData, task: Task, up: Boolean, localDelta: Float)</ID>
<ID>ComplexMethod:TaskSchedulingControls.kt$TaskSchedulingControls$private fun generateSummary()</ID>
<ID>ComplexMethod:TaskSerializer.kt$TaskSerializer$override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext): Task</ID>

View file

@ -1,179 +0,0 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
font-size: 12pt;
}
body, a, a:visited {
color: #303030;
}
#content {
padding-left: 50px;
padding-right: 50px;
padding-top: 30px;
padding-bottom: 30px;
}
#content h1 {
font-size: 160%;
margin-bottom: 10px;
}
#footer {
margin-top: 100px;
font-size: 80%;
white-space: nowrap;
}
#footer, #footer a {
color: #a0a0a0;
}
#line-wrapping-toggle {
vertical-align: middle;
}
#label-for-line-wrapping-toggle {
vertical-align: middle;
}
ul {
margin-left: 0;
}
h1, h2, h3 {
white-space: nowrap;
}
h2 {
font-size: 120%;
}
ul.tabLinks {
padding-left: 0;
padding-top: 10px;
padding-bottom: 10px;
overflow: auto;
min-width: 800px;
width: auto !important;
width: 800px;
}
ul.tabLinks li {
float: left;
height: 100%;
list-style: none;
padding-left: 10px;
padding-right: 10px;
padding-top: 5px;
padding-bottom: 5px;
margin-bottom: 0;
-moz-border-radius: 7px;
border-radius: 7px;
margin-right: 25px;
border: solid 1px #d4d4d4;
background-color: #f0f0f0;
}
ul.tabLinks li:hover {
background-color: #fafafa;
}
ul.tabLinks li.selected {
background-color: #c5f0f5;
border-color: #c5f0f5;
}
ul.tabLinks a {
font-size: 120%;
display: block;
outline: none;
text-decoration: none;
margin: 0;
padding: 0;
}
ul.tabLinks li h2 {
margin: 0;
padding: 0;
}
div.tab {
}
div.selected {
display: block;
}
div.deselected {
display: none;
}
div.tab table {
min-width: 350px;
width: auto !important;
width: 350px;
border-collapse: collapse;
}
div.tab th, div.tab table {
border-bottom: solid #d0d0d0 1px;
}
div.tab th {
text-align: left;
white-space: nowrap;
padding-left: 6em;
}
div.tab th:first-child {
padding-left: 0;
}
div.tab td {
white-space: nowrap;
padding-left: 6em;
padding-top: 5px;
padding-bottom: 5px;
}
div.tab td:first-child {
padding-left: 0;
}
div.tab td.numeric, div.tab th.numeric {
text-align: right;
}
span.code {
display: inline-block;
margin-top: 0em;
margin-bottom: 1em;
}
span.code pre {
font-size: 11pt;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
margin: 0;
background-color: #f7f7f7;
border: solid 1px #d0d0d0;
min-width: 700px;
width: auto !important;
width: 700px;
}
span.wrapped pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: break-all;
}
label.hidden {
display: none;
}

View file

@ -1,4 +0,0 @@
div.tab td.indentPath {
padding-left: 3em;
}

View file

@ -1,194 +0,0 @@
(function (window, document) {
"use strict";
var tabs = {};
function changeElementClass(element, classValue) {
if (element.getAttribute("className")) {
element.setAttribute("className", classValue);
} else {
element.setAttribute("class", classValue);
}
}
function getClassAttribute(element) {
if (element.getAttribute("className")) {
return element.getAttribute("className");
} else {
return element.getAttribute("class");
}
}
function addClass(element, classValue) {
changeElementClass(element, getClassAttribute(element) + " " + classValue);
}
function removeClass(element, classValue) {
changeElementClass(element, getClassAttribute(element).replace(classValue, ""));
}
function initTabs() {
var container = document.getElementById("tabs");
tabs.tabs = findTabs(container);
tabs.titles = findTitles(tabs.tabs);
tabs.headers = findHeaders(container);
tabs.select = select;
tabs.deselectAll = deselectAll;
tabs.select(0);
return true;
}
function getCheckBox() {
return document.getElementById("line-wrapping-toggle");
}
function getLabelForCheckBox() {
return document.getElementById("label-for-line-wrapping-toggle");
}
function findCodeBlocks() {
var spans = document.getElementById("tabs").getElementsByTagName("span");
var codeBlocks = [];
for (var i = 0; i < spans.length; ++i) {
if (spans[i].className.indexOf("code") >= 0) {
codeBlocks.push(spans[i]);
}
}
return codeBlocks;
}
function forAllCodeBlocks(operation) {
var codeBlocks = findCodeBlocks();
for (var i = 0; i < codeBlocks.length; ++i) {
operation(codeBlocks[i], "wrapped");
}
}
function toggleLineWrapping() {
var checkBox = getCheckBox();
if (checkBox.checked) {
forAllCodeBlocks(addClass);
} else {
forAllCodeBlocks(removeClass);
}
}
function initControls() {
if (findCodeBlocks().length > 0) {
var checkBox = getCheckBox();
var label = getLabelForCheckBox();
checkBox.onclick = toggleLineWrapping;
checkBox.checked = false;
removeClass(label, "hidden");
}
}
function switchTab() {
var id = this.id.substr(1);
for (var i = 0; i < tabs.tabs.length; i++) {
if (tabs.tabs[i].id === id) {
tabs.select(i);
break;
}
}
return false;
}
function select(i) {
this.deselectAll();
changeElementClass(this.tabs[i], "tab selected");
changeElementClass(this.headers[i], "selected");
while (this.headers[i].firstChild) {
this.headers[i].removeChild(this.headers[i].firstChild);
}
var h2 = document.createElement("H2");
h2.appendChild(document.createTextNode(this.titles[i]));
this.headers[i].appendChild(h2);
}
function deselectAll() {
for (var i = 0; i < this.tabs.length; i++) {
changeElementClass(this.tabs[i], "tab deselected");
changeElementClass(this.headers[i], "deselected");
while (this.headers[i].firstChild) {
this.headers[i].removeChild(this.headers[i].firstChild);
}
var a = document.createElement("A");
a.setAttribute("id", "ltab" + i);
a.setAttribute("href", "#tab" + i);
a.onclick = switchTab;
a.appendChild(document.createTextNode(this.titles[i]));
this.headers[i].appendChild(a);
}
}
function findTabs(container) {
return findChildElements(container, "DIV", "tab");
}
function findHeaders(container) {
var owner = findChildElements(container, "UL", "tabLinks");
return findChildElements(owner[0], "LI", null);
}
function findTitles(tabs) {
var titles = [];
for (var i = 0; i < tabs.length; i++) {
var tab = tabs[i];
var header = findChildElements(tab, "H2", null)[0];
header.parentNode.removeChild(header);
if (header.innerText) {
titles.push(header.innerText);
} else {
titles.push(header.textContent);
}
}
return titles;
}
function findChildElements(container, name, targetClass) {
var elements = [];
var children = container.childNodes;
for (var i = 0; i < children.length; i++) {
var child = children.item(i);
if (child.nodeType === 1 && child.nodeName === name) {
if (targetClass && child.className.indexOf(targetClass) < 0) {
continue;
}
elements.push(child);
}
}
return elements;
}
// Entry point.
window.onload = function() {
initTabs();
initControls();
};
} (window, window.document));

View file

@ -1,148 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="x-ua-compatible" content="IE=edge"/>
<title>Profile report</title>
<link href="css/base-style.css" rel="stylesheet" type="text/css"/>
<link href="css/style.css" rel="stylesheet" type="text/css"/>
<script src="js/report.js" type="text/javascript"></script>
</head>
<body>
<div id="content">
<h1>Profile report</h1>
<div id="header">
<p>Profiled build: build </p>
<p>Started on: 2016/05/09 - 17:48:55</p>
</div>
<div id="tabs">
<ul class="tabLinks">
<li>
<a href="#tab0">Summary</a>
</li>
<li>
<a href="#tab1">Configuration</a>
</li>
<li>
<a href="#tab2">Dependency Resolution</a>
</li>
<li>
<a href="#tab3">Task Execution</a>
</li>
</ul>
<div class="tab" id="tab0">
<h2>Summary</h2>
<table>
<thead>
<tr>
<th>Description</th>
<th class="numeric">Duration</th>
</tr>
</thead>
<tr>
<td>Total Build Time</td>
<td class="numeric">8.294s</td>
</tr>
<tr>
<td>Startup</td>
<td class="numeric">2.120s</td>
</tr>
<tr>
<td>Settings and BuildSrc</td>
<td class="numeric">0.225s</td>
</tr>
<tr>
<td>Loading Projects</td>
<td class="numeric">0.502s</td>
</tr>
<tr>
<td>Configuring Projects</td>
<td class="numeric">4.522s</td>
</tr>
<tr>
<td>Task Execution</td>
<td class="numeric">0s</td>
</tr>
</table>
</div>
<div class="tab" id="tab1">
<h2>Configuration</h2>
<table>
<thead>
<tr>
<th>Project</th>
<th class="numeric">Duration</th>
</tr>
</thead>
<tr>
<td>All projects</td>
<td class="numeric">4.522s</td>
</tr>
<tr>
<td>:Habitica</td>
<td class="numeric">2.818s</td>
</tr>
<tr>
<td>:</td>
<td class="numeric">1.704s</td>
</tr>
</table>
</div>
<div class="tab" id="tab2">
<h2>Dependency Resolution</h2>
<table>
<thead>
<tr>
<th>Dependencies</th>
<th class="numeric">Duration</th>
</tr>
</thead>
<tr>
<td>All dependencies</td>
<td class="numeric">0.915s</td>
</tr>
<tr>
<td>:classpath</td>
<td class="numeric">0.709s</td>
</tr>
<tr>
<td>:Habitica:classpath</td>
<td class="numeric">0.206s</td>
</tr>
</table>
</div>
<div class="tab" id="tab3">
<h2>Task Execution</h2>
<table>
<thead>
<tr>
<th>Task</th>
<th class="numeric">Duration</th>
<th>Result</th>
</tr>
</thead>
<tr>
<td>:</td>
<td class="numeric">0s</td>
<td>(total)</td>
</tr>
<tr>
<td>:Habitica</td>
<td class="numeric">0s</td>
<td>(total)</td>
</tr>
</table>
</div>
</div>
<div id="footer">
<p>
<div>
<label class="hidden" id="label-for-line-wrapping-toggle" for="line-wrapping-toggle">Wrap lines
<input id="line-wrapping-toggle" type="checkbox" autocomplete="off"/>
</label>
</div>Generated by
<a href="http://www.gradle.org">Gradle 2.13</a> at May 9, 2016 5:49:02 PM</p>
</div>
</div>
</body>
</html>

View file

@ -1,148 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="x-ua-compatible" content="IE=edge"/>
<title>Profile report</title>
<link href="css/base-style.css" rel="stylesheet" type="text/css"/>
<link href="css/style.css" rel="stylesheet" type="text/css"/>
<script src="js/report.js" type="text/javascript"></script>
</head>
<body>
<div id="content">
<h1>Profile report</h1>
<div id="header">
<p>Profiled build: build </p>
<p>Started on: 2016/05/09 - 17:50:45</p>
</div>
<div id="tabs">
<ul class="tabLinks">
<li>
<a href="#tab0">Summary</a>
</li>
<li>
<a href="#tab1">Configuration</a>
</li>
<li>
<a href="#tab2">Dependency Resolution</a>
</li>
<li>
<a href="#tab3">Task Execution</a>
</li>
</ul>
<div class="tab" id="tab0">
<h2>Summary</h2>
<table>
<thead>
<tr>
<th>Description</th>
<th class="numeric">Duration</th>
</tr>
</thead>
<tr>
<td>Total Build Time</td>
<td class="numeric">1.721s</td>
</tr>
<tr>
<td>Startup</td>
<td class="numeric">0.704s</td>
</tr>
<tr>
<td>Settings and BuildSrc</td>
<td class="numeric">0.003s</td>
</tr>
<tr>
<td>Loading Projects</td>
<td class="numeric">0.007s</td>
</tr>
<tr>
<td>Configuring Projects</td>
<td class="numeric">0.778s</td>
</tr>
<tr>
<td>Task Execution</td>
<td class="numeric">0s</td>
</tr>
</table>
</div>
<div class="tab" id="tab1">
<h2>Configuration</h2>
<table>
<thead>
<tr>
<th>Project</th>
<th class="numeric">Duration</th>
</tr>
</thead>
<tr>
<td>All projects</td>
<td class="numeric">0.778s</td>
</tr>
<tr>
<td>:Habitica</td>
<td class="numeric">0.586s</td>
</tr>
<tr>
<td>:</td>
<td class="numeric">0.192s</td>
</tr>
</table>
</div>
<div class="tab" id="tab2">
<h2>Dependency Resolution</h2>
<table>
<thead>
<tr>
<th>Dependencies</th>
<th class="numeric">Duration</th>
</tr>
</thead>
<tr>
<td>All dependencies</td>
<td class="numeric">0.318s</td>
</tr>
<tr>
<td>:classpath</td>
<td class="numeric">0.160s</td>
</tr>
<tr>
<td>:Habitica:classpath</td>
<td class="numeric">0.158s</td>
</tr>
</table>
</div>
<div class="tab" id="tab3">
<h2>Task Execution</h2>
<table>
<thead>
<tr>
<th>Task</th>
<th class="numeric">Duration</th>
<th>Result</th>
</tr>
</thead>
<tr>
<td>:</td>
<td class="numeric">0s</td>
<td>(total)</td>
</tr>
<tr>
<td>:Habitica</td>
<td class="numeric">0s</td>
<td>(total)</td>
</tr>
</table>
</div>
</div>
<div id="footer">
<p>
<div>
<label class="hidden" id="label-for-line-wrapping-toggle" for="line-wrapping-toggle">Wrap lines
<input id="line-wrapping-toggle" type="checkbox" autocomplete="off"/>
</label>
</div>Generated by
<a href="http://www.gradle.org">Gradle 2.13</a> at May 9, 2016 5:50:46 PM</p>
</div>
</div>
</body>
</html>