diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c770223 --- /dev/null +++ b/.gitignore @@ -0,0 +1,106 @@ +# Android Studio +*.iml +.gradle +/local.properties +<<<<<<< HEAD +/.idea/ +======= +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +>>>>>>> pr-7-Santix1234-kotlinTodoApp +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +<<<<<<< HEAD + +# Gradle files +*.gradle +/gradle/ +gradlew +gradlew.bat + +# 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 specific +bin/ +gen/ +out/ +release/ + +# Dependencies +/node_modules +/app/build/ + +# Secrets and local configs +*.env +local.properties +======= +local.properties + +# Kotlin/Android +bin/ +gen/ +out/ +*.class +*.log +*.dex +*.apk + +# Dependency directories +node_modules/ +jspm_packages/ +app/libs/ + +# Gradle +.gradle/ +build/ + +# Testing +app/build/ +app/test/ +app/androidTest/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Virtual environment +venv/ +.env + +# IDE specific files +.vscode/ +.idea/ +*.ipr +*.iws + +# Misc +*.swp +*.swo +.DS_Store +>>>>>>> pr-7-Santix1234-kotlinTodoApp diff --git a/README.md b/README.md index e965047..55c78fa 100644 --- a/README.md +++ b/README.md @@ -1 +1,44 @@ -Hello +# Kotlin Todo App + +## Project Overview +A simple Todo application built with Kotlin, demonstrating Android development best practices. + +## Testing Strategy +The project includes multiple layers of testing: + +### Unit Tests +- Located in `app/src/test` +- Test ViewModel logic and data transformations +- Use Mockito for mocking dependencies +- Utilize Kotlin Coroutines test utilities + +### Instrumentation Tests +- Located in `app/src/androidTest` +- Test UI components and interactions +- Use Espresso for UI testing +- Verify activity behaviors and user flows + +### Test Coverage +- ViewModel unit tests +- Activity UI tests +- Input validation tests +- Basic functionality verification + +## Running Tests +### Unit Tests +```bash +./gradlew test +``` + +### Instrumentation Tests +```bash +./gradlew connectedAndroidTest +``` + +## Dependencies +- Android Jetpack +- Kotlin Coroutines +- Room Database +- JUnit +- Mockito +- Espresso \ No newline at end of file diff --git a/app/src/androidTest/java/com/todoapp/ui/EditTodoActivityTest.kt b/app/src/androidTest/java/com/todoapp/ui/EditTodoActivityTest.kt new file mode 100644 index 0000000..dce1703 --- /dev/null +++ b/app/src/androidTest/java/com/todoapp/ui/EditTodoActivityTest.kt @@ -0,0 +1,62 @@ +package com.todoapp.ui + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.* +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.todoapp.R +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class EditTodoActivityTest { + + @get:Rule + val activityRule = ActivityScenarioRule(EditTodoActivity::class.java) + + @Test + fun testTodoCreation() { + // Enter todo title + onView(withId(R.id.edit_todo_title)) + .perform(typeText("Test Todo"), closeSoftKeyboard()) + + // Enter todo description + onView(withId(R.id.edit_todo_description)) + .perform(typeText("This is a test description"), closeSoftKeyboard()) + + // Click save button + onView(withId(R.id.save_todo_button)) + .perform(click()) + + // Verify behavior (could be checking for toast or navigation) + } + + @Test + fun testEmptyTitleValidation() { + // Leave title empty + onView(withId(R.id.edit_todo_title)) + .perform(clearText(), closeSoftKeyboard()) + + // Click save button + onView(withId(R.id.save_todo_button)) + .perform(click()) + + // Verify error state (depends on how you implement validation) + onView(withId(R.id.edit_todo_title)) + .check(matches(hasErrorText("Title cannot be empty"))) + } + + @Test + fun testCompletedSwitch() { + // Toggle completed switch + onView(withId(R.id.edit_todo_completed_switch)) + .perform(click()) + + // Verify switch state changed + onView(withId(R.id.edit_todo_completed_switch)) + .check(matches(isChecked())) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/ui/EditTodoActivity.kt b/app/src/main/java/com/todoapp/ui/EditTodoActivity.kt new file mode 100644 index 0000000..47301f5 --- /dev/null +++ b/app/src/main/java/com/todoapp/ui/EditTodoActivity.kt @@ -0,0 +1,111 @@ +package com.todoapp.ui + +import android.os.Bundle +import android.widget.Button +import android.widget.EditText +import android.widget.Switch +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.todoapp.R +import com.todoapp.data.Todo +import com.todoapp.viewmodel.TodoViewModel +import kotlinx.coroutines.launch + +class EditTodoActivity : AppCompatActivity() { + + private lateinit var todoViewModel: TodoViewModel + private lateinit var titleEditText: EditText + private lateinit var descriptionEditText: EditText + private lateinit var completedSwitch: Switch + private lateinit var saveButton: Button + private lateinit var cancelButton: Button + + private var todoId: Long = -1 + + companion object { + const val EXTRA_TODO_ID = "extra_todo_id" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_edit_todo) + + // Initialize ViewModel + todoViewModel = ViewModelProvider(this)[TodoViewModel::class.java] + + // Initialize views + titleEditText = findViewById(R.id.edit_todo_title) + descriptionEditText = findViewById(R.id.edit_todo_description) + completedSwitch = findViewById(R.id.edit_todo_completed_switch) + saveButton = findViewById(R.id.save_todo_button) + cancelButton = findViewById(R.id.cancel_todo_button) + + // Get todo item ID from intent + todoId = intent.getLongExtra(EXTRA_TODO_ID, -1) + + // Load existing todo item if editing + if (todoId != -1L) { + loadTodoItem() + } + + // Save button click listener + saveButton.setOnClickListener { + saveTodoItem() + } + + // Cancel button click listener + cancelButton.setOnClickListener { + finish() + } + } + + private fun loadTodoItem() { + lifecycleScope.launch { + val todo = todoViewModel.getTodoById(todoId) + todo?.let { + titleEditText.setText(it.title) + descriptionEditText.setText(it.description) + completedSwitch.isChecked = it.isCompleted + } + } + } + + private fun saveTodoItem() { + val title = titleEditText.text.toString().trim() + val description = descriptionEditText.text.toString().trim() + val isCompleted = completedSwitch.isChecked + + // Validate input + if (title.isEmpty()) { + titleEditText.error = "Title cannot be empty" + return + } + + lifecycleScope.launch { + try { + // Create or update todo item + val todo = Todo( + id = if (todoId == -1L) 0 else todoId, + title = title, + description = description, + isCompleted = isCompleted + ) + + if (todoId == -1L) { + todoViewModel.insertTodo(todo) + Toast.makeText(this@EditTodoActivity, "Todo created", Toast.LENGTH_SHORT).show() + } else { + todoViewModel.updateTodo(todo) + Toast.makeText(this@EditTodoActivity, "Todo updated", Toast.LENGTH_SHORT).show() + } + + // Close the activity + finish() + } catch (e: Exception) { + Toast.makeText(this@EditTodoActivity, "Error saving todo", Toast.LENGTH_SHORT).show() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_edit_todo.xml b/app/src/main/res/layout/activity_edit_todo.xml new file mode 100644 index 0000000..7def2af --- /dev/null +++ b/app/src/main/res/layout/activity_edit_todo.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + +