001package com.studentgui.test; 002 003import java.lang.reflect.Field; 004import java.nio.file.Path; 005import java.time.LocalDate; 006import java.util.Map; 007 008import static org.junit.jupiter.api.Assertions.assertTrue; 009import org.junit.jupiter.api.Test; 010 011import com.studentgui.apphelpers.Database; 012import com.studentgui.apphelpers.Helpers; 013import com.studentgui.apppages.Braille; 014import com.studentgui.apppages.JLineGraph; 015 016/** 017 * Test that generates example Braille exports (per-phase PNGs + MD/HTML) 018 * for the student "Test Student". This mirrors the export logic used in 019 * the Braille page submit handler but runs headlessly as a test so the 020 * agent can produce example files for review. 021 */ 022public class ExportBrailleReportsTest { 023 024 @Test 025 /** 026 * Generate example Braille export files (PNG per phase, markdown and HTML 027 * report) for a test student in headless mode. Useful for validating the 028 * export rendering logic in CI environments. 029 */ 030 031 public void generateBrailleExport() throws Exception { 032 // Force headless mode for chart rendering in CI-like environments 033 System.setProperty("java.awt.headless", "true"); 034 035 Helpers.createFolderHierarchy(); 036 // Ensure DB exists and schema is initialized (idempotent) 037 com.studentgui.apphelpers.SqlGenerate.initializeDatabase(); 038 039 String student = "Test Student"; 040 String progressType = "Braille"; 041 042 // Instantiate the Braille page to ensure canonical parts are created 043 JLineGraph graph = new JLineGraph(); 044 Braille braille = new Braille(student, LocalDate.now(), graph); 045 046 // Fetch historical rows + dates 047 Database.ResultsWithDates rwd = Database.fetchLatestAssessmentResultsWithDates(student, progressType, Integer.MAX_VALUE); 048 049 // Reflectively obtain the partCodes and human labels from Braille instance 050 Field pcField = Braille.class.getDeclaredField("partCodes"); 051 pcField.setAccessible(true); 052 String[] partCodes = (String[]) pcField.get(braille); 053 054 Field partsField = Braille.class.getDeclaredField("parts"); 055 partsField.setAccessible(true); 056 String[][] parts = (String[][]) partsField.get(braille); 057 String[] labels = new String[parts.length]; 058 for (int i = 0; i < parts.length; i++) labels[i] = parts[i][1]; 059 060 java.nio.file.Path out = Helpers.APP_HOME.resolve("StudentDataFiles").resolve(Helpers.safeName(student)).resolve("plots"); 061 java.nio.file.Files.createDirectories(out); 062 String baseName = "Braille-example-" + java.time.LocalDate.now().toString(); 063 064 if (rwd != null && rwd.rows != null && !rwd.rows.isEmpty()) { 065 graph.updateWithGroupedDataByDate(rwd.dates, rwd.rows, partCodes, labels); 066 } else { 067 // No historical data; create a single-row from zeros by reflection of Braille's fields 068 java.util.List<java.util.List<Integer>> rowsList = new java.util.ArrayList<>(); 069 java.util.List<Integer> latest = new java.util.ArrayList<>(); 070 for (int i = 0; i < partCodes.length; i++) latest.add(0); 071 rowsList.add(latest); 072 graph.updateWithGroupedData(rowsList, partCodes); 073 } 074 075 Map<String, Path> groups = graph.saveGroupedCharts(out, baseName, 1000, 240); 076 077 // Build simple markdown and html reports (reuse palette from JLineGraph) 078 StringBuilder md = new StringBuilder(); 079 md.append("# ").append(student).append(" - ").append(java.time.LocalDate.now().toString()).append("\n\n"); 080 for (Map.Entry<String, Path> e : groups.entrySet()) { 081 md.append("## ").append(e.getKey()).append("\n\n"); 082 md.append(".append(e.getValue().getFileName().toString()).append(")\n\n"); 083 } 084 java.nio.file.Path mdFile = out.resolve(baseName + ".md"); 085 java.nio.file.Files.writeString(mdFile, md.toString(), java.nio.charset.StandardCharsets.UTF_8); 086 087 String[] palette = JLineGraph.PALETTE_HEX; 088 java.util.LinkedHashMap<String, java.util.List<Integer>> groupsIdx = new java.util.LinkedHashMap<>(); 089 for (int i = 0; i < partCodes.length; i++) { 090 String code = partCodes[i]; 091 String grp = code != null && code.contains("_") ? code.split("_")[0] : code; 092 groupsIdx.computeIfAbsent(grp, k -> new java.util.ArrayList<>()).add(i); 093 } 094 095 StringBuilder html = new StringBuilder(); 096 html.append("<!doctype html><html><head><meta charset=\"utf-8\"><title>"); 097 html.append(student).append(" - ").append(java.time.LocalDate.now().toString()).append("</title>"); 098 html.append("<style>body{font-family:sans-serif;margin:20px;} img{max-width:100%;height:auto;border:1px solid #ccc;margin-bottom:8px;} .legend{max-height:160px;overflow:auto;border:1px solid #ddd;padding:8px;margin-bottom:24px;} .legend-item{display:flex;align-items:center;gap:8px;padding:4px 0;} .swatch{width:18px;height:12px;border:1px solid #333;display:inline-block}</style>"); 099 html.append("</head><body>"); 100 html.append("<h1>").append(student).append(" - ").append(java.time.LocalDate.now().toString()).append("</h1>"); 101 for (Map.Entry<String, Path> e2 : groups.entrySet()) { 102 String grp = e2.getKey(); 103 String imgName = e2.getValue().getFileName().toString(); 104 html.append("<h2>").append(grp).append("</h2>"); 105 html.append("<div class=\"plot\"><img src=\"./").append(imgName).append("\" alt=\"").append(grp).append("\"></div>"); 106 java.util.List<Integer> idxs = groupsIdx.getOrDefault(grp, new java.util.ArrayList<>()); 107 html.append("<div class=\"legend\">"); 108 for (int s = 0; s < idxs.size(); s++) { 109 int idx = idxs.get(s); 110 String code = partCodes[idx]; 111 String human = labels[idx]; 112 String seriesName = code + " - " + human; 113 String color = palette[s % palette.length]; 114 html.append("<div class=\"legend-item\">"); 115 html.append("<span class=\"swatch\" style=\"background:"); 116 html.append(color).append(";\"></span>"); 117 html.append("<div>").append(seriesName).append("</div></div>"); 118 } 119 html.append("</div>"); 120 } 121 html.append("</body></html>"); 122 java.nio.file.Path htmlFile = out.resolve(baseName + ".html"); 123 java.nio.file.Files.writeString(htmlFile, html.toString(), java.nio.charset.StandardCharsets.UTF_8); 124 125 System.out.println("Exported Braille report to: " + out.toAbsolutePath().toString()); 126 for (Map.Entry<String, Path> e : groups.entrySet()) System.out.println(" - " + e.getKey() + " -> " + e.getValue().getFileName()); 127 System.out.println("MD: " + mdFile.getFileName() + " HTML: " + htmlFile.getFileName()); 128 129 // Quick assertion to ensure at least one image or the md file exists 130 assertTrue(java.nio.file.Files.exists(mdFile)); 131 } 132}