Add print/export functionality for recipes

- Implement printRecipe() method in RecipeDetailCtrl to export recipes as text files
- Add directory chooser and filename input dialogs
- Integrate with existing PrintExportService for file generation
- Add comprehensive unit tests for PrintExportService methods
This commit is contained in:
Rithvik Sriram 2026-01-09 23:54:21 +01:00
commit a82c69d21c
2 changed files with 142 additions and 6 deletions

View file

@ -6,11 +6,15 @@ import client.utils.Config;
import client.utils.ConfigService;
import client.utils.LocaleAware;
import client.utils.LocaleManager;
import client.utils.PrintExportService;
import client.utils.ServerUtils;
import client.utils.WebSocketDataService;
import com.google.inject.Inject;
import commons.Recipe;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@ -19,11 +23,12 @@ import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputDialog;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import org.apache.commons.lang3.NotImplementedException;
import javafx.stage.DirectoryChooser;
/**
* Controller for the recipe detail view.
@ -246,15 +251,47 @@ public class RecipeDetailCtrl implements LocaleAware {
}
/**
* Print the currently viewed recipe.
* Gives the User a download file prompt and lets the
* user change the name of the downloaded file and
* uses printExportService to create a downloadable file.
*/
@FXML
private void printRecipe() {
// TODO: actually make it print?
throw new NotImplementedException("TODO:: Integrate with Print/Export service");
// System.out.println("Recipe printed");
}
if (recipe == null) {
return; // Do nothing if no recipe selected
}
// Open directory chooser
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Select Folder to Save Recipe");
File selectedDirectory = directoryChooser.showDialog(
printRecipeButton.getScene().getWindow());
if (selectedDirectory == null) {
return; // User cancelled
}
// Ask for filename
TextInputDialog dialog = new TextInputDialog(recipe.getName() + ".txt");
dialog.setTitle("Save Recipe");
dialog.setHeaderText("Enter filename for the recipe");
dialog.setContentText("Filename:");
Optional<String> result = dialog.showAndWait();
if (result.isPresent()) {
String filename = result.get();
if (!filename.endsWith(".txt")) {
filename = filename + ".txt";
}
// Use PrintExportService methods
String recipeText = PrintExportService.buildRecipeText(recipe);
Path dirPath = selectedDirectory.toPath();
PrintExportService.exportToFile(recipeText, dirPath, filename);
}
}
/**
* Toggles the favourite status of the currently viewed recipe in the
* application configuration and writes the changes to disk.

View file

@ -68,5 +68,104 @@ public class PrintExportTest {
()->PrintExportService.validateFolder(filePath));
assertEquals("Given path is not a folder", i.getMessage());
}
@Test
public void buildRecipeTextWithEmptyIngredientsTest(){
final long testRecipeId = 100L;
List<String> preparationSteps = new ArrayList<>();
preparationSteps.add("Just wait");
Recipe recipe = new Recipe(testRecipeId, "Empty Recipe", new ArrayList<>(), preparationSteps);
String result = PrintExportService.buildRecipeText(recipe);
assertTrue(result.contains("Title: Empty Recipe"));
assertTrue(result.contains("Recipe ID: 100"));
assertTrue(result.contains("Ingredients: "));
assertTrue(result.contains("1: Just wait"));
}
@Test
public void buildRecipeTextWithMultipleIngredientsTest(){
List<RecipeIngredient> ingredients = new ArrayList<>();
ingredients.add(DefaultValueFactory.getDefaultVagueIngredient("Flour"));
ingredients.add(DefaultValueFactory.getDefaultVagueIngredient("Sugar"));
ingredients.add(DefaultValueFactory.getDefaultVagueIngredient("Eggs"));
ingredients.add(DefaultValueFactory.getDefaultVagueIngredient("Butter"));
final long testRecipeId = 300L;
List<String> steps = new ArrayList<>();
steps.add("Mix the solid ingredients");
steps.add("Then add the wet ingredients");
steps.add("Bake");
Recipe recipe = new Recipe(testRecipeId, "Cake", ingredients, steps);
String result = PrintExportService.buildRecipeText(recipe);
assertTrue(result.contains("Some Flour"));
assertTrue(result.contains("Some Sugar"));
assertTrue(result.contains("Some Eggs"));
assertTrue(result.contains("Some Butter"));
assertTrue(result.contains("3: Bake"));
}
@Test
public void exportToFileWithComplexRecipeTest() throws IOException {
List<RecipeIngredient> ingredients = new ArrayList<>();
ingredients.add(DefaultValueFactory.getDefaultVagueIngredient("tomato"));
ingredients.add(DefaultValueFactory.getDefaultVagueIngredient("potato"));
final long testRecipeId = 500L;
List<String> steps = new ArrayList<>();
steps.add("cut up the ingredients");
steps.add("Cook");
Recipe recipe = new Recipe(testRecipeId, "stew", ingredients, steps);
String recipeData = PrintExportService.buildRecipeText(recipe);
String fileName = "stew_recipe.txt";
PrintExportService.exportToFile(recipeData, tempDir, fileName);
Path expectedFile = tempDir.resolve(fileName);
assertTrue(Files.exists(expectedFile));
String fileContent = Files.readString(expectedFile);
assertTrue(fileContent.contains("stew"));
}
@Test
public void exportFileWithInvalidFolderTest(){
Path invalidfilePath = Path.of("/invalid/folder/path");
String recipeData = "Test Data";
String fileName = "test.txt";
assertThrows(IllegalArgumentException.class,
() -> PrintExportService.exportToFile(recipeData, invalidfilePath, fileName));
}
@Test
public void exportToFileOverwritesExistingFileTest() throws IOException {
String fileName = "recipe.txt";
Path filePath = tempDir.resolve(fileName);
Files.writeString(filePath, "Old stuff");
String newContent = "New recipe data";
PrintExportService.exportToFile(newContent, tempDir, fileName);
String fileContent = Files.readString(filePath);
assertEquals(newContent, fileContent);
}
@Test
public void exportToFileSuccessTest() throws IOException {
String recipeData = "Test Recipe Content";
String fileName = "test_recipe.txt";
PrintExportService.exportToFile(recipeData, tempDir, fileName);
Path expectedFile = tempDir.resolve(fileName);
assertTrue(Files.exists(expectedFile));
String fileContent = Files.readString(expectedFile);
assertEquals(recipeData, fileContent);
}
@Test
public void buildRecipeTextWithEmptyStepsTest(){
List<RecipeIngredient> ingredients = new ArrayList<>();
ingredients.add(DefaultValueFactory.getDefaultVagueIngredient("Water"));
final long testRecipeId = 200L;
Recipe recipe = new Recipe(testRecipeId, "Water", ingredients, new ArrayList<>());
String result = PrintExportService.buildRecipeText(recipe);
assertTrue(result.contains("Title: Water"));
assertTrue(result.contains("Recipe ID: 200"));
assertTrue(result.contains("Ingredients: Some Water,"));
assertTrue(result.contains("Steps:"));
}
@Test
public void validateFolderWithGarbagePathTest(){
Path garbagePath = Path.of("/this/path/does/not/exist/at/all");
IllegalArgumentException i = assertThrows(IllegalArgumentException.class,
()->PrintExportService.validateFolder(garbagePath));
assertEquals("Folder does not exist", i.getMessage());
}
}