Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
Comment thread
vibhor1102 marked this conversation as resolved.
* Copyright (C) 2026 Kevin Buzeau
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
package com.buzbuz.smartautoclicker.core.common.overlays.menu.implementation.common

import android.os.Build
import android.view.View

import androidx.test.core.app.ApplicationProvider

import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config

import java.util.concurrent.TimeUnit

/** Ensures an animation completion cancels its delayed fallback callback. */
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.Q])
class OverlayMenuAnimationsTests {

@Test
fun showAnimation_completionDoesNotAlsoRunDelayedTimeout() {
val animations = OverlayMenuAnimations()
val view = View(ApplicationProvider.getApplicationContext())
var completionCount = 0

animations.startShowAnimation(view) { completionCount++ }

shadowOf(android.os.Looper.getMainLooper()).idleFor(300, TimeUnit.MILLISECONDS)
assertEquals(1, completionCount)

shadowOf(android.os.Looper.getMainLooper()).idleFor(1, TimeUnit.SECONDS)

assertEquals(1, completionCount)
}

@Test
fun hideAnimation_completionDoesNotAlsoRunDelayedTimeout() {
val animations = OverlayMenuAnimations()
val view = View(ApplicationProvider.getApplicationContext())
var completionCount = 0

animations.startHideAnimation(view) { completionCount++ }

shadowOf(android.os.Looper.getMainLooper()).idleFor(200, TimeUnit.MILLISECONDS)
assertEquals(1, completionCount)

shadowOf(android.os.Looper.getMainLooper()).idleFor(1, TimeUnit.SECONDS)

assertEquals(1, completionCount)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2026 Kevin Buzeau
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
package com.buzbuz.smartautoclicker.core.domain.data

import android.content.Context
import android.os.Build

import androidx.room.Room
import androidx.test.core.app.ApplicationProvider

import com.buzbuz.smartautoclicker.core.base.identifier.Identifier
import com.buzbuz.smartautoclicker.core.database.ClickDatabase
import com.buzbuz.smartautoclicker.core.domain.model.scenario.ScenarioTestsData

import kotlinx.coroutines.test.runTest

import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

/** Ensures external image cleanup only runs after a scenario transaction commits. */
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.Q])
class ScenarioDataSourceTransactionTests {

private lateinit var database: ClickDatabase
private lateinit var dataSource: ScenarioDataSource

@Before
fun setUp() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext<Context>(),
ClickDatabase::class.java,
).allowMainThreadQueries().build()
dataSource = ScenarioDataSource(database)
}

@After
fun tearDown() {
database.close()
}

@Test
fun addCompleteScenario_cleanupCallbackRunsAfterScenarioIsCommitted() = runTest {
var callbackObservedCommittedScenario = false

val insertedId = dataSource.addCompleteScenario(
scenario = ScenarioTestsData.getNewScenario(id = 0),
events = emptyList(),
counters = emptyList(),
onImageConditionsRemoved = {
callbackObservedCommittedScenario = database.scenarioDao().getScenario(1L) != null
},
)

assertEquals(1L, insertedId)
assertNotNull(database.scenarioDao().getScenario(1L))
assertEquals(true, callbackObservedCommittedScenario)
}

@Test
fun updateScenario_cleanupCallbackRunsAfterScenarioIsCommitted() = runTest {
val scenario = ScenarioTestsData.getNewScenario(id = 0)
val scenarioId = dataSource.addCompleteScenario(
scenario = scenario,
events = emptyList(),
counters = emptyList(),
onImageConditionsRemoved = {},
)!!
var callbackObservedUpdatedScenario = false

val result = dataSource.updateScenario(
scenario = scenario.copy(id = Identifier(databaseId = scenarioId), name = "Updated scenario"),
events = emptyList(),
counters = emptyList(),
onImageConditionsRemoved = {
callbackObservedUpdatedScenario = database.scenarioDao().getScenario(scenarioId)?.scenario?.name == "Updated scenario"
},
)

assertTrue(result)
assertEquals("Updated scenario", database.scenarioDao().getScenario(scenarioId)?.scenario?.name)
assertTrue(callbackObservedUpdatedScenario)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2026 Kevin Buzeau
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
package com.buzbuz.smartautoclicker.feature.smart.config.ui

import com.buzbuz.smartautoclicker.core.bitmaps.BitmapRepository
import com.buzbuz.smartautoclicker.core.domain.IRepository
import com.buzbuz.smartautoclicker.core.domain.model.counter.Counter
import com.buzbuz.smartautoclicker.core.domain.model.event.ScreenEvent
import com.buzbuz.smartautoclicker.core.domain.model.event.TriggerEvent
import com.buzbuz.smartautoclicker.core.domain.model.scenario.Scenario
import com.buzbuz.smartautoclicker.feature.smart.config.domain.EditionRepository

import io.mockk.coEvery
import io.mockk.mockk

internal suspend fun createEditionRepository(
scenario: Scenario,
screenEvents: List<ScreenEvent> = emptyList(),
triggerEvents: List<TriggerEvent> = emptyList(),
counters: List<Counter> = emptyList(),
): EditionRepository {
val repository = mockk<IRepository> {
coEvery { getScenario(scenario.id.databaseId) } returns scenario
coEvery { getScreenEvents(scenario.id.databaseId) } returns screenEvents
coEvery { getTriggerEvents(scenario.id.databaseId) } returns triggerEvents
coEvery { getCounters(scenario.id.databaseId) } returns counters
}

return EditionRepository(repository, mockk<BitmapRepository>(relaxed = true)).also {
check(it.startEdition(scenario.id.databaseId))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2026 Kevin Buzeau
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
package com.buzbuz.smartautoclicker.feature.smart.config.ui.action

import android.os.Build

import com.buzbuz.smartautoclicker.core.base.identifier.Identifier
import com.buzbuz.smartautoclicker.core.common.actions.text.appendCounterReference
import com.buzbuz.smartautoclicker.core.domain.model.AND
import com.buzbuz.smartautoclicker.core.domain.model.action.Notification
import com.buzbuz.smartautoclicker.core.domain.model.action.SetText
import com.buzbuz.smartautoclicker.core.domain.model.event.ScreenEvent
import com.buzbuz.smartautoclicker.core.domain.model.scenario.Scenario
import com.buzbuz.smartautoclicker.feature.smart.config.ui.action.notification.NotificationViewModel
import com.buzbuz.smartautoclicker.feature.smart.config.ui.action.settext.SetTextViewModel
import com.buzbuz.smartautoclicker.feature.smart.config.ui.createEditionRepository

import kotlinx.coroutines.test.runTest

import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.Q])
class ActionTextViewModelTests {

@Test
fun setTextCounterReference_isWrittenToTheEditedAction() = runTest {
val scenario = Scenario(Identifier(databaseId = 1L), "Scenario", detectionQuality = 600)
val event = ScreenEvent(Identifier(databaseId = 2L), scenario.id, "Event", AND, priority = 0, keepDetecting = false, cooldownMs = 0)
val action = SetText(Identifier(databaseId = 3L), event.id, priority = 0, text = "Hello ", validateInput = false)
val editionRepository = createEditionRepository(scenario, screenEvents = listOf(event))
editionRepository.startEventEdition(event)
editionRepository.startActionEdition(action)

SetTextViewModel(editionRepository).appendCounterReferenceToTextToWrite("score")

assertEquals("Hello ".appendCounterReference("score"), editionRepository.editionState.getEditedAction<SetText>()?.text)
}

@Test
fun notificationCounterReference_isWrittenToTheEditedAction() = runTest {
val scenario = Scenario(Identifier(databaseId = 1L), "Scenario", detectionQuality = 600)
val event = ScreenEvent(Identifier(databaseId = 2L), scenario.id, "Event", AND, priority = 0, keepDetecting = false, cooldownMs = 0)
val action = Notification(Identifier(databaseId = 3L), event.id, priority = 0, messageText = "Done: ", channelImportance = 3)
val editionRepository = createEditionRepository(scenario, screenEvents = listOf(event))
editionRepository.startEventEdition(event)
editionRepository.startActionEdition(action)

NotificationViewModel(editionRepository).setNotificationMessage("Done: ".appendCounterReference("score"))

assertEquals("Done: ".appendCounterReference("score"), editionRepository.editionState.getEditedAction<Notification>()?.messageText)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2026 Kevin Buzeau
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
package com.buzbuz.smartautoclicker.feature.smart.config.ui.condition.screen.number

import android.content.Context
import android.graphics.Rect
import android.os.Build

import com.buzbuz.smartautoclicker.core.base.identifier.Identifier
import com.buzbuz.smartautoclicker.core.common.tutorial.domain.MonitoredViewsManager
import com.buzbuz.smartautoclicker.core.domain.model.AND
import com.buzbuz.smartautoclicker.core.domain.model.condition.NumberFormatType
import com.buzbuz.smartautoclicker.core.domain.model.condition.ScreenCondition
import com.buzbuz.smartautoclicker.core.domain.model.counter.ComparisonOperation
import com.buzbuz.smartautoclicker.core.domain.model.counter.CounterOperationValue
import com.buzbuz.smartautoclicker.core.domain.model.event.ScreenEvent
import com.buzbuz.smartautoclicker.core.domain.model.scenario.Scenario
import com.buzbuz.smartautoclicker.feature.smart.config.ui.common.model.condition.UiNumberFormatDropdownItem
import com.buzbuz.smartautoclicker.feature.smart.config.ui.common.model.counter.UiCounterOperatorDropdownItem
import com.buzbuz.smartautoclicker.feature.smart.config.ui.common.model.counter.UiOperandType
import com.buzbuz.smartautoclicker.feature.smart.config.ui.createEditionRepository

import io.mockk.mockk

import kotlinx.coroutines.test.runTest

import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.Q])
class NumberConditionViewModelTests {

@Test
fun changesKeepUnrelatedNumberConditionFieldsAndUpdateSelectedValues() = runTest {
val scenario = Scenario(Identifier(databaseId = 1L), "Scenario", detectionQuality = 600)
val event = ScreenEvent(
id = Identifier(databaseId = 2L), scenarioId = scenario.id, name = "Event", conditionOperator = AND,
priority = 0, keepDetecting = false, cooldownMs = 0,
)
val condition = ScreenCondition.Number(
id = Identifier(databaseId = 3L), eventId = event.id, name = "Amount", threshold = 80, priority = 0,
detectionArea = Rect(1, 2, 30, 40), comparisonOperation = ComparisonOperation.EQUALS,
counterValue = CounterOperationValue.Number(12.0), numberFormatType = NumberFormatType.AUTO,
)
val editionRepository = createEditionRepository(scenario, screenEvents = listOf(event))
editionRepository.startEventEdition(event)
editionRepository.startConditionEdition(condition)
val viewModel = NumberConditionViewModel(mockk<Context>(), editionRepository, mockk<MonitoredViewsManager>())

viewModel.setNumberFormat(UiNumberFormatDropdownItem.CommaDecimal)
viewModel.setComparisonOperator(UiCounterOperatorDropdownItem.Comparison.GreaterOrEqualsItem)
viewModel.setThreshold(65)
viewModel.setDetectionArea(Rect(5, 6, 70, 80))
viewModel.setOperandType(UiOperandType.COUNTER)

assertEquals(
condition.copy(
threshold = 65,
detectionArea = Rect(5, 6, 70, 80),
comparisonOperation = ComparisonOperation.GREATER_OR_EQUALS,
counterValue = CounterOperationValue.Counter(""),
numberFormatType = NumberFormatType.COMMA_DECIMAL,
),
editionRepository.editionState.getEditedCondition<ScreenCondition.Number>(),
)

viewModel.setOperandType(UiOperandType.STATIC)

assertEquals(
CounterOperationValue.Number(0.0),
editionRepository.editionState.getEditedCondition<ScreenCondition.Number>()?.counterValue,
)
}
}
Loading
Loading