|
|
@@ -169,6 +169,39 @@ class ClasspathOutlookExamSprintReportRendererTest {
|
|
|
.containsPattern("<text class='chart-axis-tick-label chart-axis-tick-label-y' x='26' y='54' text-anchor='end' fill='#7f8b97' font-size='11'>5</text>");
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 覆盖展望报告三组柱状图渲染时,应在 y 轴刻度位置输出横向辅助网格线便于读数。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void renderAddsYAxisHorizontalGridLinesToOutlookBarCharts() throws Exception {
|
|
|
+ ClasspathOutlookExamSprintReportRenderer renderer = new ClasspathOutlookExamSprintReportRenderer(OBJECT_MAPPER);
|
|
|
+
|
|
|
+ String html = renderer.render(unmodeledOutlookContent(callerVocabularyPayload()), Instant.parse("2026-01-03T08:00:00Z"));
|
|
|
+
|
|
|
+ String pastPaperSvg = extractSvgByClass(html, "past-paper-column-chart");
|
|
|
+ assertThat(gridLineElements(pastPaperSvg))
|
|
|
+ .containsExactly(
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='180' x2='296' y2='180' stroke='#edf2f7' stroke-width='1'/>",
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='50' x2='296' y2='50' stroke='#edf2f7' stroke-width='1'/>");
|
|
|
+
|
|
|
+ String highFrequencySvg = extractSvgByClass(html, "high-frequency-column-chart");
|
|
|
+ assertThat(gridLineElements(highFrequencySvg))
|
|
|
+ .containsExactly(
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='180' x2='336' y2='180' stroke='#edf2f7' stroke-width='1'/>",
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='154' x2='336' y2='154' stroke='#edf2f7' stroke-width='1'/>",
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='128' x2='336' y2='128' stroke='#edf2f7' stroke-width='1'/>",
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='102' x2='336' y2='102' stroke='#edf2f7' stroke-width='1'/>",
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='76' x2='336' y2='76' stroke='#edf2f7' stroke-width='1'/>",
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='50' x2='336' y2='50' stroke='#edf2f7' stroke-width='1'/>");
|
|
|
+
|
|
|
+ String frequencyBandSvg = extractSvgByClass(html, "frequency-band-column-chart");
|
|
|
+ assertThat(gridLineElements(frequencyBandSvg))
|
|
|
+ .containsExactly(
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='180' x2='336' y2='180' stroke='#edf2f7' stroke-width='1'/>",
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='115' x2='336' y2='115' stroke='#edf2f7' stroke-width='1'/>",
|
|
|
+ "<line class='chart-grid-line chart-grid-line-y' x1='34' y1='50' x2='336' y2='50' stroke='#edf2f7' stroke-width='1'/>");
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 覆盖科学备考建议频率卡片的线性进度条时,应使用 PDF 友好的 SVG 几何图形,避免 CSS 圆角裁剪在 PDF 中变尖。
|
|
|
*/
|
|
|
@@ -426,6 +459,25 @@ class ClasspathOutlookExamSprintReportRendererTest {
|
|
|
return svgStartTags;
|
|
|
}
|
|
|
|
|
|
+ private String extractSvgByClass(String html, String cssClass) {
|
|
|
+ Pattern svgPattern = Pattern.compile("<svg\\b(?=[^>]*class='[^']*" + Pattern.quote(cssClass) + "[^']*')[\\s\\S]*?</svg>");
|
|
|
+ Matcher matcher = svgPattern.matcher(html);
|
|
|
+ if (matcher.find()) {
|
|
|
+ return matcher.group();
|
|
|
+ }
|
|
|
+ throw new AssertionError("svg should exist for class '" + cssClass + "' in HTML fragment:\n" + html);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<String> gridLineElements(String svg) {
|
|
|
+ Pattern gridLinePattern = Pattern.compile("<line class='chart-grid-line chart-grid-line-y'[^>]*/>");
|
|
|
+ Matcher matcher = gridLinePattern.matcher(svg);
|
|
|
+ List<String> gridLines = new ArrayList<>();
|
|
|
+ while (matcher.find()) {
|
|
|
+ gridLines.add(matcher.group());
|
|
|
+ }
|
|
|
+ return gridLines;
|
|
|
+ }
|
|
|
+
|
|
|
private JsonNode payloadWithCallerControlledTextSamples() throws Exception {
|
|
|
ObjectNode payload = (ObjectNode) callerVocabularyPayload();
|
|
|
payload.put("StudentName", "注入学生<script>alert(1)</script>");
|