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
1 change: 1 addition & 0 deletions src/main/kotlin/model/Recipe.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ data class Recipe(
val ingredients: List<String>? = null,
val numberOfIngredients: Int? = null
)

fun Recipe.isComplete(): Boolean {
return !ingredients.isNullOrEmpty() &&
name != null &&
Expand Down
78 changes: 43 additions & 35 deletions src/main/kotlin/ui/features_ui/SearchByNameUI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,58 @@ package org.example.ui.features_ui
import org.example.error.ThereIsNoNameException
import org.example.logic.use_case.SearchByNameUseCase
import org.example.model.Recipe
import org.example.utils.Colors
import java.util.*
import org.example.ui.Reader
import org.example.ui.RecipeFormatter
import org.example.ui.Viewer
import org.example.utils.Strings
import kotlin.collections.List

class SearchByNameUI(
private val searchByNameUseCase: SearchByNameUseCase,
private val colors: Colors
private val viewer: Viewer,
private val reader: Reader,
) {
fun show() {
val nameToSearch = Scanner(System.`in`)
println(colors.cyan("Enter the name of the Repice to search for:"))
val userInput = nameToSearch.nextLine()

displaySearchHeader()
try {
// Perform search using searchByNameUseCase
val recipes: List<Recipe>? = searchByNameUseCase.searchRecipeByName(userInput)

if (!recipes.isNullOrEmpty()) {
println(colors.blue("\n-------------------------------\n"))
println(colors.green("Found ${recipes.size} recipes:"))
println(colors.blue("-------------------------------\n"))
recipes.forEach { recipe ->
printRecipe(recipe) // Print details of each recipe
}
} else {
println(colors.red("\nSorry, we couldn't find any recipes that match the name you entered."))
}

val recipes: List<Recipe>? = searchRecipe()
if (recipes?.isNotEmpty() == true) displayRecipe(recipes)
else displayNoRecipeFoundMessage()
} catch (e: ThereIsNoNameException) {
println(colors.red("\nAn error occurred while searching: ${e.message}"))
handleSearchError(e)
}
}

private fun displaySearchHeader() {
viewer.printTitle(Strings.SEARCH_BY_NAME_TITLE.message)
}

private fun searchRecipe(): List<Recipe>? {
val userInput = reader.readInput().toString()
return searchByNameUseCase.searchRecipeByName(userInput)
}

private fun displayRecipe(recipes: List<Recipe>) {
displayFoundRecipe(recipes)
recipes.forEach {
displayRecipeDetails(it)
}
}

private fun printRecipe(recipe: Recipe) {
println(
colors.green("Recipe Details: ------------------------------------------------\nName: ${recipe.name}\n" +
"Minutes: ${recipe.minutes}\nContributor Id: ${recipe.contributorId}\n" +
"Protein = ${recipe.nutrition?.protein}\t" +
"Saturated Fat = ${recipe.nutrition?.saturatedFat}\t" +
"Carbohydrates = ${recipe.nutrition?.carbohydrates}\n" +
"Number Of Steps: ${recipe.numberOfSteps}\n" +
"Steps:\n${recipe.steps}\n" +
"Description: ${recipe.description}\n"
)
)
private fun displayFoundRecipe(recipes: List<Recipe>) {
viewer.printLoader("\n-------------------------------\n")
viewer.printCorrectOutput(Strings.FOUNT_RECIPE.formatMessage(recipes.size.toString()))
}

private fun displayRecipeDetails(recipe: Recipe) {
viewer.printCorrectOutput(RecipeFormatter.format(recipe))
}

private fun displayNoRecipeFoundMessage() {
viewer.printError(Strings.SORRY_COULD_NOT_FIND_RECIPE_MATCHES_NAME.message)
}

private fun handleSearchError(error: ThereIsNoNameException) {
viewer.printError(Strings.ERROR_OCCURRED_WHILE_SEARCHING.formatMessage(error.message))
}
}
}
10 changes: 8 additions & 2 deletions src/main/kotlin/utils/Strings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ package org.example.utils

enum class Strings(val message: String) {

SEARCH_BY_NAME_TITLE("Enter the name of the dish or part of it to search for:"),
SORRY_COULD_NOT_FIND_RECIPE_MATCHES_NAME("Sorry, we couldn't find a recipe that matches the name you entered."),
ERROR_OCCURRED_WHILE_SEARCHING("An error occurred while searching: %s"),
FOUNT_RECIPE("Number of recipes found: %s"),
ENTER_COUNTRY_OR_EXIT("Enter a country to explore its meals (or 0 to exit): "),
INVALID_COUNTRY_NAME_ENTER_LETTERS("Please enter a country name using letters only."),
INVALID_COUNTRY_NAME("Please enter a valid country name."),
TRY_ANOTHER_COUNTRY("Try another country."),
NO_MEALS_FOUND_FOR_COUNTRY("No meals found for '%s'");

fun formatMessage(country: String): String {
fun formatMessage(text: String?): String {
return when (this) {
NO_MEALS_FOUND_FOR_COUNTRY -> message.format(country)
FOUNT_RECIPE -> message.format(text.toString())
ERROR_OCCURRED_WHILE_SEARCHING -> message.format(text.toString())
NO_MEALS_FOUND_FOR_COUNTRY -> message.format(text.toString())
else -> message
}
}
Expand Down
119 changes: 119 additions & 0 deletions src/test/kotlin/ui/features_ui/SearchByNameUITest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package ui.features_ui

import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.example.error.ThereIsNoNameException
import org.example.logic.use_case.SearchByNameUseCase
import org.example.model.Recipe
import org.example.ui.Reader
import org.example.ui.RecipeFormatter
import org.example.ui.Viewer
import org.example.ui.features_ui.SearchByNameUI
import org.example.utils.Strings
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class SearchByNameUITest {

private lateinit var viewer: Viewer
private lateinit var reader: Reader
private lateinit var ui: SearchByNameUI
private lateinit var useCase: SearchByNameUseCase
private val input = "Gaza"
private val gazaRecipe = Recipe(name = "Gaza Recipe")
private val gazaRecipe2 = Recipe(name = "Original Gaza Recipe", description = "desc...")

@BeforeEach
fun setUp() {
useCase = mockk(relaxed = true)
viewer = mockk(relaxed = true)
reader = mockk(relaxed = true)
ui = SearchByNameUI(useCase, viewer, reader)
}

@Test
fun `show() should call searchRecipeByName once`() {
//When
ui.show()

//Then
verify(exactly = 1) {
useCase.searchRecipeByName("")
}
}

@Test
fun `should display title when user call show()`() {
//When
ui.show()

//Then
verify(exactly = 1) {
viewer.printTitle(Strings.SEARCH_BY_NAME_TITLE.message)
}
}

@Test
fun `should display number of recipes found when user enter recipe name and found`() {
//Given
val recipes = listOf(gazaRecipe, gazaRecipe2)
every { reader.readInput() } returns input
every { useCase.searchRecipeByName(input) } returns recipes

//When
ui.show()

//Then
verify(exactly = 1) {
viewer.printCorrectOutput(Strings.FOUNT_RECIPE.formatMessage(recipes.size.toString()))
}
}

@Test
fun `should display info recipe when user enter recipe name and found`() {
//Given
every { reader.readInput() } returns input
every { useCase.searchRecipeByName(input) } returns listOf(gazaRecipe)

//When
ui.show()

//Then
verify(exactly = 1) {
viewer.printCorrectOutput(RecipeFormatter.format(gazaRecipe))
}
}

@Test
fun `should show error message when recipe is not found`() {
//Given
every { reader.readInput() } returns input
every { useCase.searchRecipeByName(input) } returns null

//When
ui.show()

//Then
verify(exactly = 1) {
viewer.printError(Strings.SORRY_COULD_NOT_FIND_RECIPE_MATCHES_NAME.message)
}
}

@Test
fun `should print error when search throws ThereIsNoNameException`() {
// Given
every { reader.readInput() } returns input
val errorMessage = "Recipe not found"
every { useCase.searchRecipeByName(input) } throws ThereIsNoNameException(errorMessage)

// When
ui.show()

// Then
verify(exactly = 1) {
viewer.printError(Strings.ERROR_OCCURRED_WHILE_SEARCHING.formatMessage(errorMessage))
}
}

}