Initial commit

This commit is contained in:
Aryan 2026-02-24 17:37:40 +05:30
commit 6072b2ba29
844 changed files with 220532 additions and 0 deletions

View file

@ -0,0 +1,105 @@
// BaseTtsSynthesizerTest.kt
package com.aryan.reader.tts
import android.speech.tts.TextToSpeech
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BaseTtsSynthesizerTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
private lateinit var synthesizer: BaseTtsSynthesizer
private var ttsEngineAvailable = false
@Before
fun setUp() {
// Ensure TTS is available on the device/emulator before running tests
val tts = TextToSpeech(ApplicationProvider.getApplicationContext(), null)
if (tts.engines.isNotEmpty()) {
ttsEngineAvailable = true
synthesizer = BaseTtsSynthesizer(ApplicationProvider.getApplicationContext())
}
tts.shutdown()
}
@After
fun tearDown() {
if (this::synthesizer.isInitialized) {
synthesizer.shutdown()
}
}
@Test
fun initialize_initializesTtsEngineSuccessfully() {
if (!ttsEngineAvailable) return
runBlocking {
// This will throw if it fails
withTimeout(10000L) {
synthesizer.initialize()
}
// No assertion needed, success is not throwing an exception.
}
}
@Test
fun synthesizeToFile_withValidText_createsAudioFile() {
if (!ttsEngineAvailable) return
runBlocking {
withTimeout(15000L) {
synthesizer.initialize()
val (file, returnedText) = synthesizer.synthesizeToFile("This is a test.")
assertThat(file).isNotNull()
assertThat(file?.exists()).isTrue()
assertThat(file?.length()).isGreaterThan(0L)
assertThat(returnedText).isEqualTo("This is a test.")
file?.delete()
}
}
}
@Test
fun synthesizeToFile_withBlankText_returnsNullFile() {
if (!ttsEngineAvailable) return
runBlocking {
synthesizer.initialize()
val (file, returnedText) = synthesizer.synthesizeToFile(" ")
assertThat(file).isNull()
assertThat(returnedText).isEqualTo(" ")
}
}
@Test
fun synthesizeToFile_withoutInitializingFirst_initializesAndSucceeds() {
if (!ttsEngineAvailable) return
runBlocking {
withTimeout(15000L) {
val (file, returnedText) = synthesizer.synthesizeToFile("This should work.")
assertThat(file).isNotNull()
assertThat(file?.exists()).isTrue()
assertThat(file?.length()).isGreaterThan(0L)
assertThat(returnedText).isEqualTo("This should work.")
file?.delete()
}
}
}
}

View file

@ -0,0 +1,27 @@
package com.aryan.reader.tts
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
import org.junit.runner.Description
/**
* A JUnit TestRule that sets the Main dispatcher to a TestDispatcher for the duration of a test.
* This allows tests to execute coroutines on the Main dispatcher without needing a real Android environment.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class MainDispatcherRule(
private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
) : TestWatcher() {
override fun starting(description: Description) {
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description) {
Dispatchers.resetMain()
}
}

View file

@ -0,0 +1,71 @@
// TtsUtilsTest.kt
package com.aryan.reader.tts
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import androidx.test.ext.junit.runners.AndroidJUnit4
@RunWith(AndroidJUnit4::class)
class TtsUtilsTest {
@Test
fun splitTextIntoChunks_withShortText_returnsSingleChunk() {
val text = "This is a short sentence."
val chunks = splitTextIntoChunks(text, 100)
assertThat(chunks).containsExactly("This is a short sentence.")
}
@Test
fun splitTextIntoChunks_withMultipleSentences_splitsCorrectly() {
val text = "First sentence. Second sentence! Third sentence? And a fourth."
val chunks = splitTextIntoChunks(text, 20)
assertThat(chunks).containsExactly(
"First sentence.",
"Second sentence!",
"Third sentence?",
"And a fourth."
).inOrder()
}
@Test
fun splitTextIntoChunks_combinesShortSentences() {
val text = "First. Second. Third. Fourth."
val chunks = splitTextIntoChunks(text, maxLengthPerChunk = 20)
assertThat(chunks).containsExactly(
"First. Second.",
"Third. Fourth."
).inOrder()
}
@Test
fun splitTextIntoChunks_withLongSentence_doesNotSplitSentence() {
val text = "This is a very long sentence that exceeds the maximum chunk length but has no punctuation to split on."
val chunks = splitTextIntoChunks(text, 50)
assertThat(chunks).containsExactly(text)
}
@Test
fun splitTextIntoChunks_withEmptyText_returnsEmptyList() {
val text = ""
val chunks = splitTextIntoChunks(text, 100)
assertThat(chunks).isEmpty()
}
@Test
fun splitTextIntoChunks_withBlankText_returnsEmptyList() {
val text = " "
val chunks = splitTextIntoChunks(text, 100)
assertThat(chunks).isEmpty()
}
@Test
fun splitTextIntoChunks_handlesAbbreviations() {
val text = "Mr. Smith went to Washington. Dr. Jones followed."
val chunks = splitTextIntoChunks(text, 40)
assertThat(chunks).containsExactly(
"Mr. Smith went to Washington.",
"Dr. Jones followed."
).inOrder()
}
}