This commit is contained in:
@@ -7,6 +7,7 @@ import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 将 {@link CheckReport} 渲染为企微 Markdown / 控制台文本。
|
||||
@@ -53,34 +54,32 @@ public class ReportBuilder {
|
||||
appendKeyLine(sb, kc.getKeyPattern());
|
||||
String oldJson = nvl(kc.getOldSkeletonJson());
|
||||
String newJson = nvl(kc.getNewSkeletonJson());
|
||||
Set<String> oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
|
||||
Set<String> newHighlight = SkeletonAnnotator.pathsForNewSkeleton(kc.getFieldDetails());
|
||||
String oldRendered = oldJson.isEmpty()
|
||||
? "" : SkeletonAnnotator.annotateAsCodeBlock(oldJson, oldHighlight);
|
||||
String newRendered = newJson.isEmpty()
|
||||
? "" : SkeletonAnnotator.annotateAsCodeBlock(newJson, newHighlight);
|
||||
if (oldJson.isEmpty() && !newJson.isEmpty()) {
|
||||
sb.append(" value 新增为 “").append(fontWarning(newJson)).append("”\n\n");
|
||||
sb.append(" value 新增为 ").append(newRendered).append("\n\n");
|
||||
} else if (!oldJson.isEmpty() && newJson.isEmpty()) {
|
||||
sb.append(" value 原结构 “").append(fontInfo(oldJson)).append("” 已删除写入\n\n");
|
||||
sb.append(" value 原结构 ").append(oldRendered).append(" 已删除写入\n\n");
|
||||
} else {
|
||||
sb.append(" value 值从 “").append(fontInfo(oldJson)).append("”\n");
|
||||
sb.append(" 变更为 “").append(fontWarning(newJson)).append("”\n\n");
|
||||
sb.append(" value 值从 ").append(oldRendered).append('\n');
|
||||
sb.append(" 变更为 ").append(newRendered).append("\n\n");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Key 行:加粗「Key -->」+ 加粗 key 文本。
|
||||
* 避免 key 末尾 * 与 markdown ** 冲突,结尾加零宽空格再闭合。
|
||||
* Key 行:Key --> + 橙红色 key(企微 warning)。
|
||||
* 末尾加零宽空格,避免 key 中的 * 干扰后续 markdown。
|
||||
*/
|
||||
private void appendKeyLine(StringBuilder sb, String keyPattern) {
|
||||
sb.append("- **Key --> ").append(nvl(keyPattern)).append("\u200b**\n");
|
||||
}
|
||||
|
||||
/** 企微 markdown:绿色(旧骨架)。 */
|
||||
private String fontInfo(String text) {
|
||||
return "<font color=\"info\">" + text + "</font>";
|
||||
}
|
||||
|
||||
/** 企微 markdown:warning 橙红(新骨架)。 */
|
||||
private String fontWarning(String text) {
|
||||
return "<font color=\"warning\">" + text + "</font>";
|
||||
sb.append("- Key --> <font color=\"warning\">")
|
||||
.append(nvl(keyPattern))
|
||||
.append("\u200b</font>\n");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
351
src/main/java/com/codechecker/cache/report/SkeletonAnnotator.java
vendored
Normal file
351
src/main/java/com/codechecker/cache/report/SkeletonAnnotator.java
vendored
Normal file
@@ -0,0 +1,351 @@
|
||||
package com.codechecker.cache.report;
|
||||
|
||||
import com.codechecker.cache.diff.ChangeType;
|
||||
import com.codechecker.cache.diff.SchemaChange;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 在骨架 JSON 中为改动字段加企微 warning 标注。
|
||||
* <ul>
|
||||
* <li>删除字段 → 标在旧骨架</li>
|
||||
* <li>新增字段 / 新增包装层 → 标在新骨架</li>
|
||||
* <li>路径迁移 → 旧路径标旧骨架、新路径标新骨架</li>
|
||||
* </ul>
|
||||
*/
|
||||
final class SkeletonAnnotator {
|
||||
|
||||
private static final String FONT_OPEN = "<font color=\"warning\">";
|
||||
private static final String FONT_CLOSE = "</font>";
|
||||
|
||||
private SkeletonAnnotator() {
|
||||
}
|
||||
|
||||
static Set<String> pathsForOldSkeleton(List<SchemaChange> details) {
|
||||
Set<String> paths = new LinkedHashSet<>();
|
||||
if (details == null) {
|
||||
return paths;
|
||||
}
|
||||
for (SchemaChange c : details) {
|
||||
if (c == null || c.getChangeType() == null) {
|
||||
continue;
|
||||
}
|
||||
if (c.getChangeType() == ChangeType.FIELD_REMOVED) {
|
||||
addIfPresent(paths, c.getFieldPath());
|
||||
} else if (c.getChangeType() == ChangeType.FIELD_PATH_MOVED) {
|
||||
addIfPresent(paths, c.getOldValue());
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
static Set<String> pathsForNewSkeleton(List<SchemaChange> details) {
|
||||
Set<String> paths = new LinkedHashSet<>();
|
||||
if (details == null) {
|
||||
return paths;
|
||||
}
|
||||
for (SchemaChange c : details) {
|
||||
if (c == null || c.getChangeType() == null) {
|
||||
continue;
|
||||
}
|
||||
if (c.getChangeType() == ChangeType.FIELD_ADDED
|
||||
|| c.getChangeType() == ChangeType.WRAPPER_ADDED) {
|
||||
addIfPresent(paths, c.getFieldPath());
|
||||
} else if (c.getChangeType() == ChangeType.FIELD_PATH_MOVED) {
|
||||
addIfPresent(paths, c.getFieldPath());
|
||||
if (c.getNewValue() != null && !c.getNewValue().equals(c.getFieldPath())) {
|
||||
addIfPresent(paths, c.getNewValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标注路径对应的 {@code "field":value} 片段,并包成可与代码块混排的企微 Markdown。
|
||||
* 企微代码块内不解析 font,故整段用反引号分段包裹,改动项单独用 warning+反引号。
|
||||
*/
|
||||
static String annotateAsCodeBlock(String json, Set<String> highlightPaths) {
|
||||
if (json == null || json.isEmpty()) {
|
||||
return "``";
|
||||
}
|
||||
String annotated = annotate(json, highlightPaths);
|
||||
return toMixedCodeMarkdown(annotated);
|
||||
}
|
||||
|
||||
static String annotate(String json, Set<String> highlightPaths) {
|
||||
if (json == null || json.isEmpty() || highlightPaths == null || highlightPaths.isEmpty()) {
|
||||
return json == null ? "" : json;
|
||||
}
|
||||
String result = json;
|
||||
List<String> sorted = new ArrayList<>(highlightPaths);
|
||||
sorted.sort(Comparator.comparingInt(String::length).reversed());
|
||||
for (String path : sorted) {
|
||||
result = wrapOnePath(result, path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将已含 {@code <font>} 的字符串拆成:普通段走反引号代码块,改动段走 warning+反引号。
|
||||
*/
|
||||
static String toMixedCodeMarkdown(String annotated) {
|
||||
if (annotated == null || annotated.isEmpty()) {
|
||||
return "``";
|
||||
}
|
||||
if (!annotated.contains(FONT_OPEN)) {
|
||||
return "`" + annotated + "`";
|
||||
}
|
||||
StringBuilder out = new StringBuilder();
|
||||
int i = 0;
|
||||
boolean inCode = false;
|
||||
while (i < annotated.length()) {
|
||||
int start = annotated.indexOf(FONT_OPEN, i);
|
||||
if (start < 0) {
|
||||
if (!inCode) {
|
||||
out.append('`');
|
||||
inCode = true;
|
||||
}
|
||||
out.append(annotated.substring(i));
|
||||
out.append('`');
|
||||
return out.toString();
|
||||
}
|
||||
if (start > i) {
|
||||
if (!inCode) {
|
||||
out.append('`');
|
||||
inCode = true;
|
||||
}
|
||||
out.append(annotated, i, start);
|
||||
out.append('`');
|
||||
inCode = false;
|
||||
} else if (inCode) {
|
||||
out.append('`');
|
||||
inCode = false;
|
||||
}
|
||||
int contentStart = start + FONT_OPEN.length();
|
||||
int end = annotated.indexOf(FONT_CLOSE, contentStart);
|
||||
if (end < 0) {
|
||||
if (!inCode) {
|
||||
out.append('`');
|
||||
}
|
||||
out.append(annotated.substring(start)).append('`');
|
||||
return out.toString();
|
||||
}
|
||||
String frag = annotated.substring(contentStart, end);
|
||||
out.append(FONT_OPEN).append('`').append(frag).append('`').append(FONT_CLOSE);
|
||||
i = end + FONT_CLOSE.length();
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static void addIfPresent(Set<String> paths, String path) {
|
||||
if (path != null && !path.trim().isEmpty()) {
|
||||
paths.add(path.trim());
|
||||
}
|
||||
}
|
||||
|
||||
private static String wrapOnePath(String json, String path) {
|
||||
if (path == null || path.isEmpty()) {
|
||||
return json;
|
||||
}
|
||||
// 已标注过则跳过(避免重复)
|
||||
String key = lastSegment(path);
|
||||
if (key.isEmpty()) {
|
||||
return json;
|
||||
}
|
||||
int propStart = findPropertyStart(json, path);
|
||||
if (propStart < 0) {
|
||||
return json;
|
||||
}
|
||||
// 已在 font 内则跳过
|
||||
int fontBefore = json.lastIndexOf(FONT_OPEN, propStart);
|
||||
int fontCloseBefore = json.lastIndexOf(FONT_CLOSE, propStart);
|
||||
if (fontBefore > fontCloseBefore) {
|
||||
return json;
|
||||
}
|
||||
String needle = "\"" + key + "\"";
|
||||
int keyEnd = propStart + needle.length();
|
||||
int valueEnd = findValueEnd(json, keyEnd);
|
||||
if (valueEnd <= propStart) {
|
||||
return json;
|
||||
}
|
||||
String frag = json.substring(propStart, valueEnd);
|
||||
if (frag.contains(FONT_OPEN)) {
|
||||
return json;
|
||||
}
|
||||
return json.substring(0, propStart)
|
||||
+ FONT_OPEN + frag + FONT_CLOSE
|
||||
+ json.substring(valueEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按点分路径定位属性名起始下标(指向开头引号)。
|
||||
*/
|
||||
private static int findPropertyStart(String json, String path) {
|
||||
List<String> segs = splitPath(path);
|
||||
if (segs.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
int searchFrom = 0;
|
||||
int scopeEnd = json.length();
|
||||
for (int s = 0; s < segs.size(); s++) {
|
||||
String seg = segs.get(s);
|
||||
boolean last = s == segs.size() - 1;
|
||||
String needle = "\"" + seg + "\"";
|
||||
int idx = indexOfPropertyInScope(json, needle, searchFrom, scopeEnd);
|
||||
if (idx < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (last) {
|
||||
return idx;
|
||||
}
|
||||
int keyEnd = idx + needle.length();
|
||||
int valueStart = skipColon(json, keyEnd);
|
||||
if (valueStart >= json.length()) {
|
||||
return -1;
|
||||
}
|
||||
char c = json.charAt(valueStart);
|
||||
if (c == '{') {
|
||||
searchFrom = valueStart + 1;
|
||||
scopeEnd = findMatchingBracket(json, valueStart);
|
||||
if (scopeEnd < 0) {
|
||||
return -1;
|
||||
}
|
||||
} else if (c == '[') {
|
||||
// 数组:进入第一个元素对象
|
||||
int arrEnd = findMatchingBracket(json, valueStart);
|
||||
int elemObj = json.indexOf('{', valueStart);
|
||||
if (elemObj < 0 || elemObj >= arrEnd) {
|
||||
searchFrom = valueStart + 1;
|
||||
scopeEnd = arrEnd;
|
||||
} else {
|
||||
searchFrom = elemObj + 1;
|
||||
scopeEnd = findMatchingBracket(json, elemObj);
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int indexOfPropertyInScope(String json, String needle, int from, int to) {
|
||||
int idx = from;
|
||||
while (idx >= 0 && idx < to) {
|
||||
idx = json.indexOf(needle, idx);
|
||||
if (idx < 0 || idx >= to) {
|
||||
return -1;
|
||||
}
|
||||
// 粗略校验:前面应是 { 或 ,
|
||||
int p = idx - 1;
|
||||
while (p >= from && Character.isWhitespace(json.charAt(p))) {
|
||||
p--;
|
||||
}
|
||||
if (p < from || json.charAt(p) == '{' || json.charAt(p) == ',' || json.charAt(p) == '[') {
|
||||
return idx;
|
||||
}
|
||||
idx += needle.length();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int skipColon(String json, int afterKey) {
|
||||
int i = afterKey;
|
||||
while (i < json.length() && (json.charAt(i) == ':' || Character.isWhitespace(json.charAt(i)))) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private static int findMatchingBracket(String json, int openIdx) {
|
||||
if (openIdx < 0 || openIdx >= json.length()) {
|
||||
return -1;
|
||||
}
|
||||
char open = json.charAt(openIdx);
|
||||
char close = open == '{' ? '}' : ']';
|
||||
int depth = 0;
|
||||
for (int i = openIdx; i < json.length(); i++) {
|
||||
char ch = json.charAt(i);
|
||||
if (ch == '"') {
|
||||
i++;
|
||||
while (i < json.length()) {
|
||||
char x = json.charAt(i++);
|
||||
if (x == '\\' && i < json.length()) {
|
||||
i++;
|
||||
} else if (x == '"') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
if (ch == open) {
|
||||
depth++;
|
||||
} else if (ch == close) {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int findValueEnd(String json, int afterKey) {
|
||||
int i = skipColon(json, afterKey);
|
||||
if (i >= json.length()) {
|
||||
return json.length();
|
||||
}
|
||||
char c = json.charAt(i);
|
||||
if (c == '"') {
|
||||
i++;
|
||||
while (i < json.length()) {
|
||||
char ch = json.charAt(i++);
|
||||
if (ch == '\\' && i < json.length()) {
|
||||
i++;
|
||||
} else if (ch == '"') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
if (c == '{' || c == '[') {
|
||||
int end = findMatchingBracket(json, i);
|
||||
return end < 0 ? json.length() : end + 1;
|
||||
}
|
||||
while (i < json.length() && json.charAt(i) != ',' && json.charAt(i) != '}' && json.charAt(i) != ']') {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private static List<String> splitPath(String path) {
|
||||
List<String> segs = new ArrayList<>();
|
||||
if (path == null || path.isEmpty()) {
|
||||
return segs;
|
||||
}
|
||||
for (String raw : path.split("\\.")) {
|
||||
if (raw.isEmpty() || "[]".equals(raw)) {
|
||||
continue;
|
||||
}
|
||||
if (raw.endsWith("[]")) {
|
||||
String name = raw.substring(0, raw.length() - 2);
|
||||
if (!name.isEmpty()) {
|
||||
segs.add(name);
|
||||
}
|
||||
} else {
|
||||
segs.add(raw);
|
||||
}
|
||||
}
|
||||
return segs;
|
||||
}
|
||||
|
||||
private static String lastSegment(String path) {
|
||||
List<String> segs = splitPath(path);
|
||||
return segs.isEmpty() ? "" : segs.get(segs.size() - 1);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,11 @@ import com.codechecker.cache.diff.SchemaChange;
|
||||
import com.codechecker.cache.diff.Severity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -20,33 +25,69 @@ class ReportBuilderTest {
|
||||
report.setModifier("dongzi");
|
||||
report.setModifyTime("2026-07-13 16:54:17");
|
||||
|
||||
SchemaChange detail = new SchemaChange(ChangeType.WRAPPER_ADDED);
|
||||
detail.setSeverity(Severity.P0);
|
||||
detail.setKeyPattern("tenant:db:content:*");
|
||||
detail.setWriteLocation("Helper#put:10");
|
||||
detail.setFieldPath("vo");
|
||||
detail.setMessage("新增包装层 vo");
|
||||
report.getChanges().add(detail);
|
||||
SchemaChange addedMsg = new SchemaChange(ChangeType.FIELD_ADDED);
|
||||
addedMsg.setSeverity(Severity.P1);
|
||||
addedMsg.setKeyPattern("saas:period-config:migration:current");
|
||||
addedMsg.setFieldPath("message");
|
||||
addedMsg.setMessage("新增字段 message");
|
||||
|
||||
SchemaChange addedErr = new SchemaChange(ChangeType.FIELD_ADDED);
|
||||
addedErr.setSeverity(Severity.P1);
|
||||
addedErr.setKeyPattern("saas:period-config:migration:current");
|
||||
addedErr.setFieldPath("lastError");
|
||||
addedErr.setMessage("新增字段 lastError");
|
||||
|
||||
report.getChanges().add(addedMsg);
|
||||
report.getChanges().add(addedErr);
|
||||
|
||||
KeyStructureChange key = new KeyStructureChange();
|
||||
key.setKeyPattern("tenant:db:content:*");
|
||||
key.setOldSkeletonJson("{\"dbName\":\"\",\"linkList\":[{\"id\":\"\"}]}");
|
||||
key.setNewSkeletonJson("{\"vo\":{\"dbName\":\"\",\"linkList\":[{\"id\":\"\"}]},\"expiresAtMs\":0}");
|
||||
key.setSeverity(Severity.P0);
|
||||
key.setKeyPattern("saas:period-config:migration:current");
|
||||
key.setOldSkeletonJson(
|
||||
"{\"taskId\":\"\",\"status\":\"\",\"progress\":{\"totalTenants\":0}}");
|
||||
key.setNewSkeletonJson(
|
||||
"{\"taskId\":\"\",\"status\":\"\",\"progress\":{\"totalTenants\":0},\"message\":\"\",\"lastError\":\"\"}");
|
||||
key.setSeverity(Severity.P1);
|
||||
key.getFieldDetails().add(addedMsg);
|
||||
key.getFieldDetails().add(addedErr);
|
||||
report.getKeyChanges().add(key);
|
||||
|
||||
String md = new ReportBuilder("[缓存结构变更]").toMarkdown(report);
|
||||
|
||||
assertTrue(md.startsWith("## [缓存结构变更] jnpf-java-cloud"));
|
||||
assertTrue(md.contains("> **分支**: code/redis_change_detection_v1.0"));
|
||||
assertTrue(md.contains("> **时间**: 2026-07-13 16:54:17"));
|
||||
assertTrue(md.contains("- **Key --> tenant:db:content:*\u200b**"));
|
||||
assertTrue(md.contains("<font color=\"info\">{\"dbName\":\"\",\"linkList\":[{\"id\":\"\"}]}</font>"));
|
||||
assertTrue(md.contains("<font color=\"warning\">{\"vo\":{\"dbName\":\"\",\"linkList\":[{\"id\":\"\"}]},\"expiresAtMs\":0}</font>"));
|
||||
assertTrue(md.contains("- Key --> <font color=\"warning\">saas:period-config:migration:current\u200b</font>"));
|
||||
assertTrue(md.contains("value 值从"));
|
||||
assertTrue(md.contains("变更为"));
|
||||
// 新增字段仅在新骨架中 warning 标注
|
||||
assertTrue(md.contains("<font color=\"warning\">`\"message\":\"\"`</font>"));
|
||||
assertTrue(md.contains("<font color=\"warning\">`\"lastError\":\"\"`</font>"));
|
||||
// 旧骨架不应出现这两个字段的 warning
|
||||
int oldSection = md.indexOf("value 值从");
|
||||
int newSection = md.indexOf("变更为");
|
||||
String oldPart = md.substring(oldSection, newSection);
|
||||
assertFalse(oldPart.contains("<font color=\"warning\">`\"message\":\"\"`</font>"));
|
||||
assertFalse(md.contains("### P0"));
|
||||
assertFalse(md.contains("位置"));
|
||||
assertFalse(md.contains("模式"));
|
||||
assertFalse(md.contains("汇总"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removedFieldsHighlightedOnlyInOldSkeleton() {
|
||||
SchemaChange removed = new SchemaChange(ChangeType.FIELD_REMOVED);
|
||||
removed.setFieldPath("lastError");
|
||||
removed.setMessage("删除字段 lastError");
|
||||
|
||||
String oldJson = "{\"taskId\":\"\",\"lastError\":\"\"}";
|
||||
String newJson = "{\"taskId\":\"\"}";
|
||||
Set<String> oldPaths = SkeletonAnnotator.pathsForOldSkeleton(Collections.singletonList(removed));
|
||||
Set<String> newPaths = SkeletonAnnotator.pathsForNewSkeleton(Collections.singletonList(removed));
|
||||
|
||||
assertEquals(Collections.singleton("lastError"), oldPaths);
|
||||
assertTrue(newPaths.isEmpty());
|
||||
|
||||
String oldMd = SkeletonAnnotator.annotateAsCodeBlock(oldJson, oldPaths);
|
||||
String newMd = SkeletonAnnotator.annotateAsCodeBlock(newJson, newPaths);
|
||||
assertTrue(oldMd.contains("<font color=\"warning\">`\"lastError\":\"\"`</font>"));
|
||||
assertFalse(newMd.contains("lastError"));
|
||||
assertTrue(newMd.startsWith("`"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,6 +101,7 @@ class ReportBuilderTest {
|
||||
detail.setSeverity(Severity.P0);
|
||||
detail.setKeyPattern("k1");
|
||||
detail.setWriteLocation("Foo#bar:1");
|
||||
detail.setFieldPath("x");
|
||||
detail.setMessage("删除字段 x");
|
||||
report.getChanges().add(detail);
|
||||
|
||||
@@ -67,6 +109,7 @@ class ReportBuilderTest {
|
||||
key.setKeyPattern("k1");
|
||||
key.setOldSkeletonJson("{\"x\":\"\"}");
|
||||
key.setNewSkeletonJson("{}");
|
||||
key.getFieldDetails().add(detail);
|
||||
report.getKeyChanges().add(key);
|
||||
|
||||
String console = new ReportBuilder("[缓存结构变更]").toConsole(report);
|
||||
@@ -75,8 +118,16 @@ class ReportBuilderTest {
|
||||
assertTrue(console.contains("**删除字段**: x"));
|
||||
assertTrue(console.contains("======== 企微 Markdown ========"));
|
||||
assertTrue(console.contains("value 值从"));
|
||||
assertTrue(console.contains("- **Key --> k1\u200b**"));
|
||||
assertTrue(console.contains("<font color=\"info\">"));
|
||||
assertTrue(console.contains("<font color=\"warning\">"));
|
||||
assertTrue(console.contains("<font color=\"warning\">`\"x\":\"\"`</font>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathAwareNestedHighlight() {
|
||||
String json = "{\"vo\":{\"dbName\":\"\",\"id\":\"\"},\"expiresAtMs\":0}";
|
||||
Set<String> paths = new LinkedHashSet<>();
|
||||
paths.add("vo.dbName");
|
||||
String out = SkeletonAnnotator.annotate(json, paths);
|
||||
assertTrue(out.contains("<font color=\"warning\">\"dbName\":\"\"</font>"));
|
||||
assertFalse(out.contains("<font color=\"warning\">\"id\":\"\"</font>"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user