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
40 changes: 40 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Android Studio
*.iml
.gradle
/local.properties
/.idea/
.DS_Store
/build
/captures
.externalNativeBuild
.cxx

# Gradle files
gradle/
gradlew
gradlew.bat

# Dependency directories
/node_modules/

# Compiled class files
*.class

# Log files
*.log

# Package Files
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# Virtual machine crash logs
hs_err_pid*

# Android build output
bin/
out/
23 changes: 23 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
}

dependencies {
// Room Database
implementation("androidx.room:room-runtime:2.5.1")
implementation("androidx.room:room-ktx:2.5.1")
kapt("androidx.room:room-compiler:2.5.1")

// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")

// Testing
testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.13.5")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
17 changes: 17 additions & 0 deletions src/main/java/com/example/todoapp/data/dao/TodoDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.todoapp.data.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.example.todoapp.data.model.Todo
import kotlinx.coroutines.flow.Flow

@Dao
interface TodoDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTodo(todo: Todo): Long

@Query("SELECT * FROM todos WHERE id = :todoId")
fun getTodoById(todoId: Long): Flow<Todo?>
}
25 changes: 25 additions & 0 deletions src/main/java/com/example/todoapp/data/model/Todo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.example.todoapp.data.model

import androidx.room.Entity
import androidx.room.PrimaryKey
import java.time.LocalDateTime

@Entity(tableName = "todos")
data class Todo(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val title: String,
val description: String? = null,
val isCompleted: Boolean = false,
val createdAt: LocalDateTime = LocalDateTime.now(),
val updatedAt: LocalDateTime = LocalDateTime.now()
) {
// Validate todo item input
init {
require(title.isNotBlank()) { "Todo title cannot be blank" }
require(title.length <= 100) { "Todo title cannot exceed 100 characters" }
description?.let {
require(it.length <= 500) { "Todo description cannot exceed 500 characters" }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.todoapp.data.repository

import com.example.todoapp.data.dao.TodoDao
import com.example.todoapp.data.model.Todo
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class TodoRepository @Inject constructor(
private val todoDao: TodoDao
) {
suspend fun addTodo(todo: Todo): Long {
return todoDao.insertTodo(todo)
}

fun getTodoById(todoId: Long): Flow<Todo?> {
return todoDao.getTodoById(todoId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.example.todoapp.data.repository

import com.example.todoapp.data.dao.TodoDao
import com.example.todoapp.data.model.Todo
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

class TodoRepositoryTest {
private val todoDao = mockk<TodoDao>()
private val todoRepository = TodoRepository(todoDao)

@Test
fun `addTodo should save todo item successfully`() = runBlocking {
// Arrange
val todo = Todo(title = "Test Todo")
coEvery { todoDao.insertTodo(todo) } returns 1L

// Act
val result = todoRepository.addTodo(todo)

// Assert
assertEquals(1L, result)
}

@Test(expected = IllegalArgumentException::class)
fun `addTodo should throw exception for blank title`() = runBlocking {
// Arrange
val todo = Todo(title = "")

// Act & Assert
todoRepository.addTodo(todo)
}

@Test(expected = IllegalArgumentException::class)
fun `addTodo should throw exception for title exceeding max length`() = runBlocking {
// Arrange
val longTitle = "a".repeat(101)
val todo = Todo(title = longTitle)

// Act & Assert
todoRepository.addTodo(todo)
}
}