Skip to content
Merged
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
44 changes: 44 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Android CI

# Define when the pipeline should run
on:
push:
branches: [ "master", "main" ]
pull_request:
branches: [ "master", "main" ]

jobs:
build_and_test:
runs-on: ubuntu-latest

steps:
# Download the code from the repository
- name: Checkout code
uses: actions/checkout@v4

# Set up Java 17
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle

# Make the Gradle wrapper executable
- name: Grant execute permission for gradlew
run: chmod +x gradlew

# Run Local Unit Tests
- name: Run local Unit Tests
run: ./gradlew testDebugUnitTest

# Build the APK to ensure the project compiles correctly
- name: Build Debug APK
run: ./gradlew assembleDebug

# Upload the APK as an artifact so you can download it from GitHub
- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: app-debug
path: app/build/outputs/apk/debug/app-debug.apk
35 changes: 23 additions & 12 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "11"
jvmTarget = "17"
}
buildFeatures {
compose = true
Expand All @@ -50,13 +50,10 @@ dependencies {
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
// Hilt
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
Expand All @@ -73,12 +70,26 @@ dependencies {
// Navigation
implementation(libs.navigation.compose)

// Tests
// Local Unit Tests
testImplementation(libs.androidx.test.core)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
testImplementation(libs.androidx.arch.core.testing)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.google.truth)
testImplementation(libs.okhttp.mockwebserver)
testImplementation(libs.mockk)
debugImplementation(libs.androidx.compose.ui.test.manifest)

// Instrumentation tests
androidTestImplementation(libs.hilt.android.testing)
kspAndroidTest(libs.hilt.compiler)
androidTestImplementation(libs.junit)
androidTestImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.androidx.arch.core.testing)
androidTestImplementation(libs.google.truth)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.test.core.ktx)
androidTestImplementation(libs.okhttp.mockwebserver)
androidTestImplementation(libs.mockk.android)
androidTestImplementation(libs.androidx.test.runner)
}
Original file line number Diff line number Diff line change
@@ -1,52 +1,43 @@
package com.bober.notesapp.data.repository

import android.app.Application
import com.bober.notesapp.R
import com.bober.notesapp.domain.model.Note
import com.bober.notesapp.domain.repository.NoteRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import javax.inject.Inject

class FakeNoteRepository @Inject constructor(
private val app: Application
): NoteRepository {
class FakeNoteRepository @Inject constructor(): NoteRepository {

private val fakeContent =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vestibulum vel arcu vel ullamcorper. Integer venenatis diam ac facilisis accumsan. Phasellus lorem felis, sollicitudin non justo ut, posuere dignissim nunc. Nunc dignissim volutpat mauris quis rhoncus. Curabitur tempor ipsum non lacus porttitor mollis. Etiam dictum eros eros, at auctor augue consequat a. Fusce a arcu ac est maximus hendrerit at lacinia tellus.In feugiat dui non laoreet vestibulum. Nunc vulputate id mauris a iaculis. Curabitur vitae tempus justo. Morbi a est et sapien maximus porta eu ac ante. Cras gravida feugiat elementum. Vestibulum dictum volutpat est sit amet commodo. Suspendisse sed lectus magna. Pellentesque eget augue sed ipsum aliquet tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nulla sed auctor arcu. Proin ligula nisl, auctor ac ornare eu, mattis quis lorem. Phasellus et gravida metus, vitae tincidunt nibh. Nam tempus, libero porttitor mattis suscipit, neque neque viverra nunc, non ultrices nibh odio non turpis. Vestibulum dui sem, congue vel tellus ac, convallis iaculis mauris. Integer vulputate, augue bibendum congue ullamcorper, lacus enim luctus magna, sed iaculis magna enim eget augue."

// This replaces a real database during development or testing
private val _notesFlow = MutableStateFlow<List<Note>>(
private val notesFlow = MutableStateFlow(
listOf(
Note(id = 1, title = "First Note", content = app.getString(R.string.fake_content), timestamp = 1L, color = 0xFFFFAB91.toInt()),
Note(id = 2, title = "Second Note", content = app.getString(R.string.fake_content), timestamp = 2L, color = 0xFFE7ED9B.toInt()),
Note(id = 3, title = "Third Note", content = app.getString(R.string.fake_content), timestamp = 3L, color = 0xFFD7AEFB.toInt())
Note(id = 1, title = "First Note", content = fakeContent, timestamp = 1L, color = 0xFFFFAB91.toInt()),
Note(id = 2, title = "Second Note", content = fakeContent, timestamp = 2L, color = 0xFFE7ED9B.toInt()),
Note(id = 3, title = "Third Note", content = fakeContent, timestamp = 3L, color = 0xFFD7AEFB.toInt())
)
)

override fun getNotes(): Flow<List<Note>> {
return _notesFlow.asStateFlow()
return notesFlow
}

override suspend fun getNoteById(id: Int): Note? {
return _notesFlow.value.find { it.id == id }
return notesFlow.value.find { it.id == id }
}

override suspend fun insertNote(note: Note) {
_notesFlow.update { currentNotes ->
val existingNote = currentNotes.find { it.id == note.id }
if (existingNote != null) {
currentNotes.map { if (it.id == note.id) note else it }
} else {
val newId = (currentNotes.maxOfOrNull { it.id ?: 0 } ?: 0) + 1
currentNotes + note.copy(id = newId)
}
notesFlow.update { currentNotes ->
currentNotes + note
}
}

override suspend fun deleteNote(note: Note) {
println("Repository: Deleting note ${note.id}")
_notesFlow.update { currentNotes ->
notesFlow.update { currentNotes ->
currentNotes.filter { it.id != note.id }
}
println("Repository: Remaining notes: ${_notesFlow.value.size}")
}
}
7 changes: 1 addition & 6 deletions app/src/main/java/com/bober/notesapp/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ object AppModule {
@Singleton
fun provideNoteRepository(db : NoteDatabase): NoteRepository{
return NoteRepositoryImpl(db.noteDao())
// return FakeNoteRepository()
}

// @Provides
// @Singleton
// fun provideNoteRepository(app: Application): NoteRepository{
// return FakeNoteRepository(app)
// }
}
4 changes: 1 addition & 3 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<resources>
<string name="app_name">NotesApp</string>
<string name="fake_content">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vestibulum vel arcu vel ullamcorper. Integer venenatis diam ac facilisis accumsan. Phasellus lorem felis, sollicitudin non justo ut, posuere dignissim nunc. Nunc dignissim volutpat mauris quis rhoncus. Curabitur tempor ipsum non lacus porttitor mollis. Etiam dictum eros eros, at auctor augue consequat a. Fusce a arcu ac est maximus hendrerit at lacinia tellus.

In feugiat dui non laoreet vestibulum. Nunc vulputate id mauris a iaculis. Curabitur vitae tempus justo. Morbi a est et sapien maximus porta eu ac ante. Cras gravida feugiat elementum. Vestibulum dictum volutpat est sit amet commodo. Suspendisse sed lectus magna. Pellentesque eget augue sed ipsum aliquet tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nulla sed auctor arcu. Proin ligula nisl, auctor ac ornare eu, mattis quis lorem. Phasellus et gravida metus, vitae tincidunt nibh. Nam tempus, libero porttitor mattis suscipit, neque neque viverra nunc, non ultrices nibh odio non turpis. Vestibulum dui sem, congue vel tellus ac, convallis iaculis mauris. Integer vulputate, augue bibendum congue ullamcorper, lacus enim luctus magna, sed iaculis magna enim eget augue.</string>
<string name="fake_content">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vestibulum vel arcu vel ullamcorper. Integer venenatis diam ac facilisis accumsan. Phasellus lorem felis, sollicitudin non justo ut, posuere dignissim nunc. Nunc dignissim volutpat mauris quis rhoncus. Curabitur tempor ipsum non lacus porttitor mollis. Etiam dictum eros eros, at auctor augue consequat a. Fusce a arcu ac est maximus hendrerit at lacinia tellus.In feugiat dui non laoreet vestibulum. Nunc vulputate id mauris a iaculis. Curabitur vitae tempus justo. Morbi a est et sapien maximus porta eu ac ante. Cras gravida feugiat elementum. Vestibulum dictum volutpat est sit amet commodo. Suspendisse sed lectus magna. Pellentesque eget augue sed ipsum aliquet tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nulla sed auctor arcu. Proin ligula nisl, auctor ac ornare eu, mattis quis lorem. Phasellus et gravida metus, vitae tincidunt nibh. Nam tempus, libero porttitor mattis suscipit, neque neque viverra nunc, non ultrices nibh odio non turpis. Vestibulum dui sem, congue vel tellus ac, convallis iaculis mauris. Integer vulputate, augue bibendum congue ullamcorper, lacus enim luctus magna, sed iaculis magna enim eget augue.</string>
</resources>
17 changes: 0 additions & 17 deletions app/src/test/java/com/bober/notesapp/ExampleUnitTest.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.bober.notesapp.data.repository

import com.bober.notesapp.domain.model.Note
import com.bober.notesapp.domain.repository.NoteRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject

class FakeTestNoteRepository @Inject constructor(): NoteRepository {

private val notes = mutableListOf<Note>()

override fun getNotes(): Flow<List<Note>> {
return flow { emit(notes) }
}

override suspend fun getNoteById(id: Int): Note? {
return notes.find { it.id == id }
}

override suspend fun insertNote(note: Note) {
notes.add(note)
}

override suspend fun deleteNote(note: Note) {
notes.remove(note)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.bober.notesapp.domain.use_case

import com.bober.notesapp.data.repository.FakeTestNoteRepository
import com.bober.notesapp.domain.model.InvalidNoteException
import com.bober.notesapp.domain.model.Note
import kotlinx.coroutines.test.runTest
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test

class AddNoteTest {
private lateinit var addNote: AddNote
private lateinit var fakeRepository: FakeTestNoteRepository

@Before
fun setUp() {
fakeRepository = FakeTestNoteRepository()
addNote = AddNote(fakeRepository)
}

@Test
fun `Insert valid note, successfully adds to repository`() = runTest {
val note = Note(
title = "Not blank",
content = "Not blank",
timestamp = 1L,
color = 1,
id = 99
)
addNote(note)

val result = fakeRepository.getNoteById(99)
assertThat(result).isEqualTo(note)
}

@Test
fun `Insert note with blank title, throws exception`() = runTest {
val note = Note(
title = "",
content = "Not blank",
timestamp = 1L,
color = 1,
id = 99
)

val exception = try {
addNote(note)
null
} catch (e: InvalidNoteException){
e
}
assertThat(exception?.message).isEqualTo("The title of the note can't be empty.")
}

@Test
fun `Insert note with blank content, throws exception`() = runTest {
val note = Note(
title = "Not blank",
content = "",
timestamp = 1L,
color = 1,
id = 99
)

val exception = try {
addNote(note)
null
} catch (e: InvalidNoteException){
e
}
assertThat(exception?.message).isEqualTo("The content of the note can't be empty.")
}
}
Loading
Loading