From 1d5899ce7e4756468314e3a52d5f025b31352780 Mon Sep 17 00:00:00 2001 From: nanhaoluo <3075912108@qq.com> Date: Fri, 23 Jan 2026 23:12:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Mermaid=20?= =?UTF-8?q?=E5=BA=93=E5=8A=A0=E8=BD=BD=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=99=8D?= =?UTF-8?q?=E7=BA=A7=E5=A4=84=E7=90=86=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加多 CDN 备选方案(jsdelivr、unpkg、本地镜像) - 实现递归加载逻辑,主 CDN 失败时自动尝试备用 CDN - 添加 onerror 事件处理,捕获库加载失败 - 所有 CDN 失败时显示友好的错误提示 - 在错误提示中保留原始代码供用户查看 - 添加详细的控制台日志输出 - 创建 PHP 和 HTML 测试文件验证功能 - 暴露 MermaidRenderer 到全局作用域供降级处理使用 Requirements: 1.4, 2.3, 7.1, 7.2, 7.3, 7.4, 7.5 --- .kiro/specs/mermaid-support/design.md | 977 ++++++++++++++++++++ .kiro/specs/mermaid-support/requirements.md | 143 +++ .kiro/specs/mermaid-support/tasks.md | 2 +- .kiro/steering/mermaid-removal-summary.md | 51 + argontheme.js | 3 + functions.php | 166 +++- tests/test-mermaid-fallback.html | 374 ++++++++ tests/test-mermaid-fallback.php | 249 +++++ 8 files changed, 1962 insertions(+), 3 deletions(-) create mode 100644 .kiro/specs/mermaid-support/design.md create mode 100644 .kiro/specs/mermaid-support/requirements.md create mode 100644 .kiro/steering/mermaid-removal-summary.md create mode 100644 tests/test-mermaid-fallback.html create mode 100644 tests/test-mermaid-fallback.php diff --git a/.kiro/specs/mermaid-support/design.md b/.kiro/specs/mermaid-support/design.md new file mode 100644 index 0000000..f0fbef2 --- /dev/null +++ b/.kiro/specs/mermaid-support/design.md @@ -0,0 +1,977 @@ +# Design Document: Mermaid 图表支持 + +## Overview + +本设计文档描述了在 Argon WordPress 主题中集成 Mermaid 图表支持的技术方案。由于 WP-Markdown 编辑器的特殊渲染方式(将 Mermaid 代码块保存为单行,缺少真正的换行符),直接集成 Mermaid 存在技术障碍。 + +本设计采用**插件兼容方案**,通过支持主流 Mermaid WordPress 插件(如 WP Githuber MD、Markdown Block 等)来实现功能,同时提供主题级别的样式优化、夜间模式适配和性能优化。 + +### 核心设计原则 + +1. **插件优先**:依赖成熟的 Mermaid 插件处理代码解析和渲染 +2. **主题增强**:提供样式优化、主题适配和性能优化 +3. **灵活配置**:支持 CDN/本地加载、多种主题、调试模式 +4. **优雅降级**:加载失败时提供备用方案和友好提示 +5. **性能优先**:按需加载、异步加载、缓存优化 + +## Architecture + +### 系统架构图 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ WordPress 前端页面 │ +└─────────────────────────────────────────────────────────────┘ + │ + ├─ 包含 Mermaid 代码块? + │ + ┌─────────┴─────────┐ + │ │ + 是 否 + │ │ + ▼ ▼ + ┌──────────────────┐ ┌──────────┐ + │ 加载 Mermaid 库 │ │ 不加载 │ + └──────────────────┘ └──────────┘ + │ + ├─ CDN 模式 / 本地模式 + │ + ▼ + ┌──────────────────┐ + │ Mermaid.js 库 │ + └──────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ 代码块检测器 │ + │ - class="mermaid"│ + │ - language="mermaid"│ + │ - data-lang="mermaid"│ + └──────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ 渲染引擎 │ + │ - 初始化配置 │ + │ - 主题适配 │ + │ - 错误处理 │ + └──────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ 样式增强器 │ + │ - 容器样式 │ + │ - 响应式适配 │ + │ - 夜间模式 │ + └──────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ 渲染后的 SVG │ + └──────────────────┘ +``` + +### 组件交互流程 + +``` +用户访问页面 + │ + ▼ +WordPress 渲染页面 + │ + ▼ +主题检测页面内容 + │ + ├─ 是否包含 Mermaid 代码块? + │ + ▼ (是) +加载 Mermaid 库 (CDN/本地) + │ + ▼ +DOMContentLoaded 事件触发 + │ + ▼ +初始化 Mermaid 配置 + │ + ├─ 设置主题 (日间/夜间) + ├─ 设置安全级别 + └─ 设置错误处理 + │ + ▼ +检测所有 Mermaid 代码块 + │ + ▼ +批量渲染图表 + │ + ├─ 成功 → 应用样式增强 + └─ 失败 → 显示错误提示 + │ + ▼ +监听主题切换事件 + │ + ▼ +重新渲染图表 (如需要) +``` + + +## Components and Interfaces + +### 1. 配置管理组件 (Configuration Manager) + +**职责**:管理 Mermaid 相关的所有配置选项 + +**接口**: +```php +// 获取配置选项 +function argon_get_mermaid_option($option_name, $default = null) + +// 保存配置选项 +function argon_update_mermaid_option($option_name, $value) + +// 验证 CDN 地址格式 +function argon_validate_mermaid_cdn_url($url) + +// 获取当前主题模式对应的 Mermaid 主题 +function argon_get_mermaid_theme() +``` + +**配置选项**: +- `argon_enable_mermaid`: 启用/禁用 Mermaid 支持 (true/false) +- `argon_mermaid_cdn_source`: CDN 来源 (jsdelivr/unpkg/custom/local) +- `argon_mermaid_cdn_custom_url`: 自定义 CDN 地址 +- `argon_mermaid_theme`: 图表主题 (default/dark/forest/neutral/auto) +- `argon_mermaid_use_local`: 使用本地镜像 (true/false) +- `argon_mermaid_debug_mode`: 调试模式 (true/false) + +### 2. 库加载器 (Library Loader) + +**职责**:负责检测页面内容并按需加载 Mermaid 库 + +**接口**: +```php +// 检测页面是否包含 Mermaid 代码块 +function argon_has_mermaid_content($content) + +// 加载 Mermaid 库 +function argon_enqueue_mermaid_scripts() + +// 获取 Mermaid 库 URL +function argon_get_mermaid_library_url() +``` + +**实现逻辑**: +1. 在 `wp_enqueue_scripts` 钩子中检查当前页面内容 +2. 使用正则表达式检测 Mermaid 代码块标记 +3. 如果检测到,根据配置加载对应的库文件 +4. 添加 async 或 defer 属性实现异步加载 + +**CDN 地址映射**: +```php +$cdn_urls = [ + 'jsdelivr' => 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js', + 'unpkg' => 'https://unpkg.com/mermaid@10/dist/mermaid.min.js', + 'local' => get_template_directory_uri() . '/assets/vendor/mermaid/mermaid.min.js' +]; +``` + +### 3. 渲染引擎初始化器 (Render Engine Initializer) + +**职责**:初始化 Mermaid 配置并启动渲染 + +**接口**: +```javascript +// 初始化 Mermaid 配置 +function initMermaidConfig() + +// 获取当前主题对应的 Mermaid 主题 +function getMermaidTheme() + +// 渲染所有 Mermaid 图表 +function renderAllMermaidCharts() + +// 重新渲染图表(主题切换时) +function reRenderMermaidCharts() +``` + +**Mermaid 配置对象**: +```javascript +{ + startOnLoad: false, // 手动控制渲染时机 + theme: 'default', // 根据页面主题动态设置 + securityLevel: 'loose', // 允许 HTML 标签 + logLevel: 'error', // 生产环境使用 error,调试模式使用 debug + flowchart: { + useMaxWidth: true, + htmlLabels: true + } +} +``` + +### 4. 代码块检测器 (Code Block Detector) + +**职责**:识别页面中的 Mermaid 代码块 + +**接口**: +```javascript +// 检测所有 Mermaid 代码块 +function detectMermaidBlocks() + +// 检查元素是否为 Mermaid 代码块 +function isMermaidBlock(element) + +// 提取代码块内容 +function extractMermaidCode(element) +``` + +**检测规则**(优先级从高到低): +1. `
` - 标准格式 +2. `
` - Markdown 格式
+3. `
` - 自定义属性格式
+4. `` - 简化格式
+
+**特殊处理**:
+- 忽略 HTML 注释中的代码块
+- 处理 WP-Markdown 生成的 `` 格式
+- 解码转义字符(`\n`, `\"`, `\'`)
+
+### 5. 样式增强器 (Style Enhancer)
+
+**职责**:为渲染后的图表添加主题样式
+
+**接口**:
+```javascript
+// 应用容器样式
+function applyMermaidContainerStyles(container)
+
+// 应用响应式样式
+function applyResponsiveStyles(container)
+
+// 应用夜间模式样式
+function applyDarkModeStyles(container, isDarkMode)
+```
+
+**CSS 样式类**:
+```css
+.mermaid-container {
+    background: var(--card-background);
+    border-radius: 8px;
+    padding: 20px;
+    margin: 20px 0;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+    overflow-x: auto;
+    max-width: 100%;
+}
+
+.mermaid-container svg {
+    max-width: 100%;
+    height: auto;
+}
+
+html.darkmode .mermaid-container {
+    background: var(--card-background-dark);
+    box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+}
+```
+
+### 6. 错误处理器 (Error Handler)
+
+**职责**:处理渲染错误并显示友好提示
+
+**接口**:
+```javascript
+// 处理渲染错误
+function handleMermaidError(error, element)
+
+// 显示错误提示
+function showErrorMessage(element, errorInfo)
+
+// 记录调试信息
+function logDebugInfo(message, data)
+```
+
+**错误提示格式**:
+```html
+
+
⚠️
+
Mermaid 图表渲染失败
+
错误类型: 语法错误
+
行号: 3
+
+ 查看原始代码 +
...
+
+
+``` + +### 7. 主题切换监听器 (Theme Switch Listener) + +**职责**:监听主题模式切换并重新渲染图表 + +**接口**: +```javascript +// 监听主题切换事件 +function listenThemeSwitch() + +// 主题切换回调 +function onThemeSwitched(isDarkMode) + +// 批量重新渲染 +function batchReRender(elements) +``` + +**实现方式**: +- 监听 Argon 主题的 `argon:theme-switched` 自定义事件 +- 监听 `html` 元素的 `darkmode` class 变化(MutationObserver) +- 使用防抖避免频繁重新渲染 + +### 8. 插件兼容层 (Plugin Compatibility Layer) + +**职责**:检测并兼容主流 Mermaid 插件 + +**接口**: +```php +// 检测已安装的 Mermaid 插件 +function argon_detect_mermaid_plugins() + +// 检查是否已加载 Mermaid 库 +function argon_is_mermaid_loaded() + +// 避免重复加载 +function argon_prevent_duplicate_loading() +``` + +**支持的插件**: +1. **WP Githuber MD** - 检测 `wp-githuber-md` 插件 +2. **Markdown Block** - 检测 Gutenberg Mermaid 块 +3. **Code Syntax Block** - 检测代码高亮插件的 Mermaid 支持 + +**兼容策略**: +- 如果插件已加载 Mermaid 库,主题不再重复加载 +- 主题只提供样式增强和主题适配 +- 通过 `window.mermaid` 对象检测库是否已加载 + + +## Data Models + +### 1. Mermaid 配置对象 + +```javascript +interface MermaidConfig { + enabled: boolean; // 是否启用 + cdnSource: string; // CDN 来源: 'jsdelivr' | 'unpkg' | 'custom' | 'local' + customCdnUrl: string; // 自定义 CDN 地址 + theme: string; // 图表主题: 'default' | 'dark' | 'forest' | 'neutral' | 'auto' + useLocal: boolean; // 是否使用本地镜像 + debugMode: boolean; // 调试模式 + autoThemeSwitch: boolean; // 自动切换主题 +} +``` + +### 2. 代码块元素对象 + +```javascript +interface MermaidBlock { + element: HTMLElement; // DOM 元素 + code: string; // Mermaid 代码 + type: string; // 代码块类型: 'div' | 'pre-code' | 'custom' + rendered: boolean; // 是否已渲染 + error: Error | null; // 渲染错误 +} +``` + +### 3. 渲染结果对象 + +```javascript +interface RenderResult { + success: boolean; // 是否成功 + svg: string; // 渲染后的 SVG + error: { + type: string; // 错误类型 + message: string; // 错误信息 + line: number; // 错误行号 + } | null; +} +``` + +### 4. 插件检测结果 + +```php +interface PluginDetectionResult { + 'wp-githuber-md': boolean; // WP Githuber MD + 'markdown-block': boolean; // Markdown Block + 'code-syntax-block': boolean; // Code Syntax Block + 'mermaid-loaded': boolean; // Mermaid 库是否已加载 +} +``` + +### 5. 错误信息对象 + +```javascript +interface ErrorInfo { + type: string; // 错误类型: 'syntax' | 'render' | 'load' + message: string; // 错误信息 + line: number | null; // 错误行号 + code: string; // 原始代码 + timestamp: number; // 时间戳 +} +``` + +## Correctness Properties + +*属性是一种特征或行为,应该在系统的所有有效执行中保持为真——本质上是关于系统应该做什么的正式陈述。属性作为人类可读规范和机器可验证正确性保证之间的桥梁。* + +### Property 1: 按需加载库 + +*对于任意* WordPress 页面,当且仅当页面内容包含 Mermaid 代码块时,主题应该加载 Mermaid JavaScript 库。 + +**Validates: Requirements 1.1, 1.5, 8.1** + +### Property 2: CDN 地址正确性 + +*对于任意* CDN 配置选项(jsdelivr、unpkg、custom、local),生成的脚本 URL 应该与配置选项对应的 CDN 地址匹配。 + +**Validates: Requirements 1.2, 1.3** + +### Property 3: 代码块识别完整性 + +*对于任意* 包含 Mermaid 标记的 HTML 元素(class="mermaid"、language="mermaid"、data-lang="mermaid"),检测器应该能够识别并提取其中的 Mermaid 代码。 + +**Validates: Requirements 10.1, 10.2, 10.3** + +### Property 4: 渲染成功后替换内容 + +*对于任意* 成功渲染的 Mermaid 图表,原始代码块文本应该被移除,并替换为渲染后的 SVG 图表。 + +**Validates: Requirements 2.4** + +### Property 5: 错误时保留原始代码 + +*对于任意* 渲染失败的 Mermaid 代码块,系统应该显示错误提示信息,并保留原始代码块以便用户修正。 + +**Validates: Requirements 7.1, 7.4** + +### Property 6: 主题模式自动切换 + +*对于任意* 页面主题模式切换(日间↔夜间),当配置为自动切换时,所有 Mermaid 图表应该重新渲染并使用对应的图表主题(浅色↔深色)。 + +**Validates: Requirements 4.1, 4.2, 4.3** + +### Property 7: 自定义主题优先级 + +*对于任意* 图表,当管理员设置了自定义图表主题时,应该使用自定义主题而不是根据页面主题自动切换。 + +**Validates: Requirements 4.5** + +### Property 8: 响应式容器宽度 + +*对于任意* 渲染后的 Mermaid 图表容器,其最大宽度应该设置为 100%,并且当图表宽度超过容器时应该启用横向滚动。 + +**Validates: Requirements 3.1, 3.3** + +### Property 9: 移动端自适应 + +*对于任意* 屏幕宽度小于 768px 的设备,Mermaid 图表应该自动调整大小以适应屏幕宽度,不应该出现横向溢出。 + +**Validates: Requirements 3.2** + +### Property 10: 夜间模式样式适配 + +*对于任意* Mermaid 图表容器,在夜间模式下应该应用深色背景和边框样式,与页面整体风格保持一致。 + +**Validates: Requirements 6.5** + +### Property 11: 卡片内边距 + +*对于任意* 在卡片中显示的 Mermaid 图表,容器应该添加适当的内边距(padding),确保图表与卡片边缘有足够的间距。 + +**Validates: Requirements 6.3** + +### Property 12: CDN 地址验证 + +*对于任意* 用户输入的自定义 CDN 地址,保存前应该验证其格式是否为有效的 URL,并且以 `.js` 结尾。 + +**Validates: Requirements 5.5** + +### Property 13: 避免重复加载 + +*对于任意* 页面,当检测到已有 Mermaid 插件加载了 Mermaid 库时,主题不应该重复加载该库。 + +**Validates: Requirements 9.4** + +### Property 14: 错误信息完整性 + +*对于任意* Mermaid 解析错误,错误提示信息应该包含错误类型和详细的错误描述,帮助用户定位问题。 + +**Validates: Requirements 7.3** + +### Property 15: 批量渲染性能 + +*对于任意* 包含多个 Mermaid 图表的页面,所有图表应该在一次 DOM 遍历中批量收集,然后批量渲染,而不是逐个渲染。 + +**Validates: Requirements 8.4** + +### Property 16: 渲染缓存 + +*对于任意* 已成功渲染的 Mermaid 图表,在页面生命周期内不应该重复渲染,除非主题模式发生切换。 + +**Validates: Requirements 8.3** + +### Property 17: 代码块优先级 + +*对于任意* 同时包含多个 Mermaid 标记的元素(如同时有 class="mermaid" 和 data-lang="mermaid"),应该优先使用 class 属性进行识别。 + +**Validates: Requirements 10.4** + +### Property 18: 忽略注释代码块 + +*对于任意* 被 HTML 注释包裹的 Mermaid 代码块,检测器应该忽略它们,不进行渲染。 + +**Validates: Requirements 10.5** + +## Error Handling + +### 1. 库加载失败 + +**场景**:CDN 不可用或网络问题导致 Mermaid 库加载失败 + +**处理策略**: +1. 监听脚本 `onerror` 事件 +2. 在控制台输出详细错误信息 +3. 尝试降级到备用 CDN(jsdelivr → unpkg → local) +4. 如果所有 CDN 都失败,显示全局提示信息 + +**实现**: +```javascript +function loadMermaidWithFallback(urls, index = 0) { + if (index >= urls.length) { + console.error('[Argon Mermaid] 所有 CDN 加载失败'); + showGlobalError('Mermaid 库加载失败,请检查网络连接'); + return; + } + + const script = document.createElement('script'); + script.src = urls[index]; + script.async = true; + + script.onerror = () => { + console.warn(`[Argon Mermaid] CDN ${urls[index]} 加载失败,尝试备用 CDN`); + loadMermaidWithFallback(urls, index + 1); + }; + + script.onload = () => { + console.log(`[Argon Mermaid] 成功从 ${urls[index]} 加载库`); + initMermaid(); + }; + + document.head.appendChild(script); +} +``` + +### 2. 代码解析错误 + +**场景**:Mermaid 代码语法错误导致解析失败 + +**处理策略**: +1. 捕获 Mermaid 渲染异常 +2. 提取错误类型和行号信息 +3. 在原代码块位置显示友好的错误提示 +4. 保留原始代码供用户查看和修正 +5. 在调试模式下输出详细堆栈信息 + +**错误提示 UI**: +```html +
+
+ ⚠️ + 图表渲染失败 +
+
+

错误类型: 语法错误

+

Expecting 'NEWLINE', 'SPACE', got 'GRAPH'

+

位置: 第 3 行

+
+
+ 查看原始代码 +
...
+
+
+``` + +### 3. 配置验证错误 + +**场景**:管理员输入无效的配置选项 + +**处理策略**: +1. 在保存前验证所有配置项 +2. CDN URL 格式验证(必须是有效 URL 且以 .js 结尾) +3. 主题名称验证(必须是预定义的主题之一) +4. 显示具体的验证错误信息 +5. 阻止保存无效配置 + +**验证函数**: +```php +function argon_validate_mermaid_settings($settings) { + $errors = []; + + // 验证 CDN URL + if ($settings['cdn_source'] === 'custom') { + $url = $settings['custom_cdn_url']; + if (!filter_var($url, FILTER_VALIDATE_URL)) { + $errors[] = 'CDN 地址格式无效'; + } elseif (!preg_match('/\.js$/', $url)) { + $errors[] = 'CDN 地址必须以 .js 结尾'; + } + } + + // 验证主题名称 + $valid_themes = ['default', 'dark', 'forest', 'neutral', 'auto']; + if (!in_array($settings['theme'], $valid_themes)) { + $errors[] = '无效的图表主题'; + } + + return $errors; +} +``` + +### 4. 插件冲突 + +**场景**:多个插件同时加载 Mermaid 库导致冲突 + +**处理策略**: +1. 在加载前检测 `window.mermaid` 是否已存在 +2. 如果已存在,跳过库加载,只应用样式增强 +3. 记录检测结果到控制台 +4. 在设置页显示插件兼容性状态 + +**检测逻辑**: +```javascript +function checkMermaidLoaded() { + if (typeof window.mermaid !== 'undefined') { + console.log('[Argon Mermaid] 检测到 Mermaid 库已由其他插件加载'); + return true; + } + return false; +} +``` + +### 5. 主题切换异常 + +**场景**:主题切换时重新渲染失败 + +**处理策略**: +1. 使用 try-catch 包裹重新渲染逻辑 +2. 如果重新渲染失败,保留原有图表 +3. 在控制台输出警告信息 +4. 不影响页面其他功能 + +**实现**: +```javascript +function reRenderOnThemeSwitch() { + const charts = document.querySelectorAll('.mermaid-rendered'); + + charts.forEach(chart => { + try { + const code = chart.dataset.mermaidCode; + const newTheme = getMermaidTheme(); + + mermaid.initialize({ theme: newTheme }); + mermaid.render('mermaid-' + Date.now(), code, (svg) => { + chart.innerHTML = svg; + }); + } catch (error) { + console.warn('[Argon Mermaid] 重新渲染失败,保留原图表', error); + } + }); +} +``` + + +## Testing Strategy + +### 测试方法概述 + +本功能采用**双重测试策略**: +- **单元测试**:验证具体示例、边缘情况和错误条件 +- **属性测试**:验证跨所有输入的通用属性 + +两者互补且都是全面覆盖所需的: +- 单元测试捕获具体的 bug +- 属性测试验证一般正确性 + +### 单元测试策略 + +单元测试应专注于: +- **具体示例**:演示正确行为的特定案例 +- **集成点**:组件之间的交互 +- **边缘情况和错误条件**:特殊场景处理 + +**测试框架**:使用 PHPUnit(PHP 部分)和 Jest(JavaScript 部分) + +**PHP 单元测试示例**: +```php +// 测试 CDN URL 验证 +public function test_validate_cdn_url_with_valid_url() { + $url = 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js'; + $this->assertTrue(argon_validate_mermaid_cdn_url($url)); +} + +public function test_validate_cdn_url_with_invalid_url() { + $url = 'not-a-valid-url'; + $this->assertFalse(argon_validate_mermaid_cdn_url($url)); +} + +// 测试代码块检测 +public function test_has_mermaid_content_with_div_class() { + $content = '
flowchart TD
'; + $this->assertTrue(argon_has_mermaid_content($content)); +} + +public function test_has_mermaid_content_without_mermaid() { + $content = '

Regular paragraph

'; + $this->assertFalse(argon_has_mermaid_content($content)); +} +``` + +**JavaScript 单元测试示例**: +```javascript +// 测试主题获取 +test('getMermaidTheme returns dark theme in dark mode', () => { + document.documentElement.classList.add('darkmode'); + expect(getMermaidTheme()).toBe('dark'); +}); + +test('getMermaidTheme returns default theme in light mode', () => { + document.documentElement.classList.remove('darkmode'); + expect(getMermaidTheme()).toBe('default'); +}); + +// 测试代码块检测 +test('isMermaidBlock detects div with mermaid class', () => { + const div = document.createElement('div'); + div.className = 'mermaid'; + expect(isMermaidBlock(div)).toBe(true); +}); + +test('isMermaidBlock ignores regular div', () => { + const div = document.createElement('div'); + expect(isMermaidBlock(div)).toBe(false); +}); +``` + +### 属性测试策略 + +**测试框架**:使用 fast-check(JavaScript)进行属性测试 + +**配置要求**: +- 每个属性测试最少运行 100 次迭代 +- 每个测试必须引用设计文档中的属性 +- 标签格式:`Feature: mermaid-support, Property {number}: {property_text}` + +**属性测试示例**: + +```javascript +// Feature: mermaid-support, Property 1: 按需加载库 +// 对于任意 WordPress 页面,当且仅当页面内容包含 Mermaid 代码块时,主题应该加载 Mermaid JavaScript 库 +fc.assert( + fc.property( + fc.string(), // 生成随机页面内容 + fc.boolean(), // 是否包含 Mermaid 代码块 + (content, hasMermaid) => { + const pageContent = hasMermaid + ? content + '
flowchart TD
' + : content; + + const shouldLoad = argon_has_mermaid_content(pageContent); + expect(shouldLoad).toBe(hasMermaid); + } + ), + { numRuns: 100 } +); + +// Feature: mermaid-support, Property 2: CDN 地址正确性 +// 对于任意 CDN 配置选项,生成的脚本 URL 应该与配置选项对应的 CDN 地址匹配 +fc.assert( + fc.property( + fc.constantFrom('jsdelivr', 'unpkg', 'local'), + (cdnSource) => { + const expectedUrls = { + 'jsdelivr': 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js', + 'unpkg': 'https://unpkg.com/mermaid@10/dist/mermaid.min.js', + 'local': '/wp-content/themes/argon/assets/vendor/mermaid/mermaid.min.js' + }; + + const actualUrl = argon_get_mermaid_library_url(cdnSource); + expect(actualUrl).toContain(expectedUrls[cdnSource]); + } + ), + { numRuns: 100 } +); + +// Feature: mermaid-support, Property 3: 代码块识别完整性 +// 对于任意包含 Mermaid 标记的 HTML 元素,检测器应该能够识别并提取其中的 Mermaid 代码 +fc.assert( + fc.property( + fc.constantFrom('class', 'language', 'data-lang'), + fc.string({ minLength: 10 }), + (markType, code) => { + let element; + switch (markType) { + case 'class': + element = document.createElement('div'); + element.className = 'mermaid'; + element.textContent = code; + break; + case 'language': + element = document.createElement('pre'); + const codeEl = document.createElement('code'); + codeEl.className = 'language-mermaid'; + codeEl.textContent = code; + element.appendChild(codeEl); + break; + case 'data-lang': + element = document.createElement('pre'); + element.setAttribute('data-lang', 'mermaid'); + element.textContent = code; + break; + } + + expect(isMermaidBlock(element)).toBe(true); + expect(extractMermaidCode(element)).toBe(code); + } + ), + { numRuns: 100 } +); + +// Feature: mermaid-support, Property 6: 主题模式自动切换 +// 对于任意页面主题模式切换,当配置为自动切换时,所有 Mermaid 图表应该重新渲染并使用对应的图表主题 +fc.assert( + fc.property( + fc.boolean(), // 初始主题模式 + fc.integer({ min: 1, max: 10 }), // 图表数量 + (initialDarkMode, chartCount) => { + // 设置初始主题 + if (initialDarkMode) { + document.documentElement.classList.add('darkmode'); + } else { + document.documentElement.classList.remove('darkmode'); + } + + // 创建多个图表 + const charts = []; + for (let i = 0; i < chartCount; i++) { + const chart = document.createElement('div'); + chart.className = 'mermaid-rendered'; + chart.dataset.mermaidCode = 'flowchart TD\nA-->B'; + document.body.appendChild(chart); + charts.push(chart); + } + + // 切换主题 + const newDarkMode = !initialDarkMode; + if (newDarkMode) { + document.documentElement.classList.add('darkmode'); + } else { + document.documentElement.classList.remove('darkmode'); + } + + // 触发重新渲染 + reRenderOnThemeSwitch(); + + // 验证所有图表都使用了新主题 + const expectedTheme = newDarkMode ? 'dark' : 'default'; + charts.forEach(chart => { + expect(chart.dataset.currentTheme).toBe(expectedTheme); + }); + + // 清理 + charts.forEach(chart => chart.remove()); + } + ), + { numRuns: 100 } +); + +// Feature: mermaid-support, Property 12: CDN 地址验证 +// 对于任意用户输入的自定义 CDN 地址,保存前应该验证其格式是否为有效的 URL,并且以 .js 结尾 +fc.assert( + fc.property( + fc.webUrl(), // 生成随机 URL + fc.constantFrom('.js', '.css', '.json', ''), // 不同的文件扩展名 + (baseUrl, extension) => { + const url = baseUrl + extension; + const isValid = argon_validate_mermaid_cdn_url(url); + + // 只有以 .js 结尾的有效 URL 才应该通过验证 + const shouldBeValid = extension === '.js'; + expect(isValid).toBe(shouldBeValid); + } + ), + { numRuns: 100 } +); + +// Feature: mermaid-support, Property 15: 批量渲染性能 +// 对于任意包含多个 Mermaid 图表的页面,所有图表应该在一次 DOM 遍历中批量收集,然后批量渲染 +fc.assert( + fc.property( + fc.integer({ min: 1, max: 20 }), // 图表数量 + (chartCount) => { + // 创建多个图表 + for (let i = 0; i < chartCount; i++) { + const div = document.createElement('div'); + div.className = 'mermaid'; + div.textContent = `flowchart TD\nA${i}-->B${i}`; + document.body.appendChild(div); + } + + // 记录 DOM 查询次数 + let queryCount = 0; + const originalQuerySelectorAll = document.querySelectorAll; + document.querySelectorAll = function(...args) { + queryCount++; + return originalQuerySelectorAll.apply(this, args); + }; + + // 执行批量渲染 + renderAllMermaidCharts(); + + // 恢复原始方法 + document.querySelectorAll = originalQuerySelectorAll; + + // 验证只进行了一次 DOM 查询 + expect(queryCount).toBe(1); + + // 清理 + document.querySelectorAll('.mermaid').forEach(el => el.remove()); + } + ), + { numRuns: 100 } +); +``` + +### 集成测试 + +**测试场景**: +1. **完整渲染流程**:从页面加载到图表显示的完整流程 +2. **插件兼容性**:与 WP Githuber MD、Markdown Block 等插件的集成 +3. **主题切换**:日间/夜间模式切换时的图表重新渲染 +4. **错误恢复**:CDN 加载失败时的降级处理 + +**测试工具**:使用 Playwright 或 Puppeteer 进行端到端测试 + +### 手动测试清单 + +由于某些需求难以自动化测试,需要进行手动验证: + +- [ ] 图表颜色与页面背景色有足够的对比度(Requirements 4.4) +- [ ] 图表容器样式与主题整体风格一致(Requirements 6.2) +- [ ] 所有 Mermaid 官方图表类型都能正确渲染(Requirements 2.2) +- [ ] 移动端显示效果良好(Requirements 3.2) +- [ ] 错误提示信息友好易懂(Requirements 7.1) +- [ ] 设置页预览功能正常工作(Requirements 5.6) + +### 测试覆盖率目标 + +- **PHP 代码覆盖率**:≥ 80% +- **JavaScript 代码覆盖率**:≥ 85% +- **属性测试覆盖**:所有 18 个正确性属性 +- **单元测试覆盖**:所有核心函数和边缘情况 + diff --git a/.kiro/specs/mermaid-support/requirements.md b/.kiro/specs/mermaid-support/requirements.md new file mode 100644 index 0000000..bc62a15 --- /dev/null +++ b/.kiro/specs/mermaid-support/requirements.md @@ -0,0 +1,143 @@ +# Requirements Document + +## Introduction + +本文档定义了在 Argon WordPress 主题中实现 Mermaid 图表支持功能的需求。Mermaid 是一个基于 JavaScript 的图表和流程图生成工具,允许用户通过文本描述创建各种类型的可视化图表。 + +由于 WP-Markdown 编辑器的特殊渲染方式(将整个 Mermaid 代码块保存为单行,没有真正的换行符),直接集成 Mermaid 存在技术障碍。本需求文档采用插件集成方案,通过支持 Mermaid 的 WordPress 插件来实现功能。 + +## Glossary + +- **Mermaid**: 基于 JavaScript 的图表生成库,支持流程图、时序图、类图等多种图表类型 +- **WP-Markdown**: WordPress Markdown 编辑器,用于在 WordPress 中编辑 Markdown 格式的内容 +- **Theme**: Argon WordPress 主题系统 +- **Admin**: WordPress 后台管理员 +- **CDN**: 内容分发网络,用于加载外部 JavaScript 库 +- **Render_Engine**: Mermaid 图表渲染引擎 +- **Code_Block**: Markdown 代码块,用于包含 Mermaid 图表定义 +- **Theme_Mode**: 主题模式,包括日间模式和夜间模式 +- **Settings_Page**: Argon 主题设置页面 + +## Requirements + +### Requirement 1: Mermaid 库加载 + +**User Story:** 作为开发者,我希望主题能够正确加载 Mermaid 库,以便在文章中渲染图表。 + +#### Acceptance Criteria + +1. WHEN Admin 在设置页启用 Mermaid 支持 THEN THE Theme SHALL 在前端页面加载 Mermaid JavaScript 库 +2. WHERE CDN 模式被选择 WHEN 页面加载时 THEN THE Theme SHALL 从指定的 CDN 地址加载 Mermaid 库 +3. WHERE 本地镜像模式被选择 WHEN 页面加载时 THEN THE Theme SHALL 从主题目录加载本地 Mermaid 库文件 +4. WHEN Mermaid 库加载失败 THEN THE Theme SHALL 在控制台输出错误信息并降级到备用 CDN +5. THE Theme SHALL 仅在包含 Mermaid 代码块的页面加载 Mermaid 库 + +### Requirement 2: 图表渲染 + +**User Story:** 作为内容创作者,我希望在文章中使用 Mermaid 语法创建图表,并在前端正确显示。 + +#### Acceptance Criteria + +1. WHEN 文章包含 Mermaid 代码块 THEN THE Render_Engine SHALL 将代码块渲染为可视化图表 +2. THE Render_Engine SHALL 支持所有 Mermaid 官方图表类型(流程图、时序图、类图、状态图、甘特图、饼图、Git 图等) +3. WHEN Mermaid 代码语法错误 THEN THE Render_Engine SHALL 显示友好的错误提示信息 +4. WHEN 图表渲染成功 THEN THE Theme SHALL 移除原始代码块文本并显示渲染后的 SVG 图表 +5. THE Render_Engine SHALL 在 DOM 加载完成后初始化并渲染所有图表 + +### Requirement 3: 响应式设计 + +**User Story:** 作为用户,我希望图表能够在不同设备上正确显示,以便在移动端也能查看。 + +#### Acceptance Criteria + +1. WHEN 图表宽度超过容器宽度 THEN THE Theme SHALL 启用横向滚动功能 +2. WHEN 用户在移动设备上查看 THEN THE Theme SHALL 自动调整图表大小以适应屏幕宽度 +3. THE Theme SHALL 为图表容器设置最大宽度为 100% +4. WHEN 图表高度超过视口高度 THEN THE Theme SHALL 保持图表完整性不进行裁剪 +5. THE Theme SHALL 在图表容器上应用响应式 CSS 样式 + +### Requirement 4: 主题模式适配 + +**User Story:** 作为用户,我希望图表能够适配夜间模式,以便在不同主题下都有良好的视觉效果。 + +#### Acceptance Criteria + +1. WHEN 用户切换到夜间模式 THEN THE Theme SHALL 自动切换 Mermaid 图表主题为深色主题 +2. WHEN 用户切换到日间模式 THEN THE Theme SHALL 自动切换 Mermaid 图表主题为浅色主题 +3. THE Theme SHALL 在主题切换时重新渲染所有 Mermaid 图表 +4. THE Theme SHALL 确保图表颜色与页面背景色有足够的对比度 +5. WHERE Admin 设置了自定义图表主题 THEN THE Theme SHALL 使用自定义主题而不是自动切换 + +### Requirement 5: 后台设置 + +**User Story:** 作为管理员,我希望能够在后台配置 Mermaid 功能,以便根据需求调整行为。 + +#### Acceptance Criteria + +1. THE Settings_Page SHALL 提供启用或禁用 Mermaid 支持的开关选项 +2. THE Settings_Page SHALL 提供 CDN 地址选择选项(jsDelivr、unpkg、自定义) +3. THE Settings_Page SHALL 提供图表主题选择选项(default、dark、forest、neutral) +4. THE Settings_Page SHALL 提供本地镜像开关选项 +5. WHEN Admin 保存设置 THEN THE Theme SHALL 验证 CDN 地址格式的有效性 +6. THE Settings_Page SHALL 提供 Mermaid 配置预览功能 + +### Requirement 6: 样式优化 + +**User Story:** 作为内容创作者,我希望图表有美观的样式,以便提升文章的视觉质量。 + +#### Acceptance Criteria + +1. THE Theme SHALL 为图表容器添加背景色、圆角和阴影样式 +2. THE Theme SHALL 确保图表容器样式与主题整体风格一致 +3. WHEN 图表在卡片中显示 THEN THE Theme SHALL 添加适当的内边距 +4. THE Theme SHALL 为图表添加淡入动画效果 +5. THE Theme SHALL 在夜间模式下调整图表容器的背景色和边框色 + +### Requirement 7: 错误处理 + +**User Story:** 作为开发者,我希望系统能够优雅地处理错误,以便快速定位和解决问题。 + +#### Acceptance Criteria + +1. WHEN Mermaid 代码解析失败 THEN THE Theme SHALL 在图表位置显示错误提示信息 +2. WHEN Mermaid 库加载失败 THEN THE Theme SHALL 在控制台输出详细的错误日志 +3. THE Theme SHALL 在错误提示中包含错误类型和行号信息 +4. WHEN 发生错误 THEN THE Theme SHALL 保留原始代码块以便用户修正 +5. THE Theme SHALL 提供调试模式选项以输出详细的渲染过程信息 + +### Requirement 8: 性能优化 + +**User Story:** 作为用户,我希望图表加载不影响页面性能,以便获得流畅的浏览体验。 + +#### Acceptance Criteria + +1. THE Theme SHALL 仅在包含 Mermaid 代码块的页面加载 Mermaid 库 +2. THE Theme SHALL 使用异步方式加载 Mermaid 库 +3. THE Theme SHALL 缓存已渲染的图表以避免重复渲染 +4. WHEN 页面包含多个图表 THEN THE Theme SHALL 批量渲染以提高性能 +5. THE Theme SHALL 使用 CDN 加速 Mermaid 库的加载速度 + +### Requirement 9: 插件兼容性 + +**User Story:** 作为管理员,我希望 Mermaid 功能能够与常用的 WordPress 插件兼容,以便灵活选择编辑器。 + +#### Acceptance Criteria + +1. THE Theme SHALL 支持通过 WP Githuber MD 插件创建的 Mermaid 图表 +2. THE Theme SHALL 支持通过 Markdown Block 插件创建的 Mermaid 图表 +3. THE Theme SHALL 支持通过 Code Syntax Block 插件创建的 Mermaid 图表 +4. WHEN 检测到多个 Mermaid 插件 THEN THE Theme SHALL 避免重复加载 Mermaid 库 +5. THE Theme SHALL 提供插件兼容性检测功能 + +### Requirement 10: 代码块识别 + +**User Story:** 作为开发者,我希望系统能够准确识别 Mermaid 代码块,以便正确渲染图表。 + +#### Acceptance Criteria + +1. THE Theme SHALL 识别 class 属性为 "mermaid" 的 div 元素作为 Mermaid 代码块 +2. THE Theme SHALL 识别 language 属性为 "mermaid" 的 pre 或 code 元素作为 Mermaid 代码块 +3. THE Theme SHALL 识别 data-lang 属性为 "mermaid" 的元素作为 Mermaid 代码块 +4. WHEN 代码块同时包含多个识别标记 THEN THE Theme SHALL 优先使用 class 属性 +5. THE Theme SHALL 忽略被注释掉的 Mermaid 代码块 + diff --git a/.kiro/specs/mermaid-support/tasks.md b/.kiro/specs/mermaid-support/tasks.md index 8cd1b01..f2ebc72 100644 --- a/.kiro/specs/mermaid-support/tasks.md +++ b/.kiro/specs/mermaid-support/tasks.md @@ -69,7 +69,7 @@ - **Validates: Requirements 8.4** - _Requirements: 8.4_ -- [~] 5. 实现错误处理机制 +- [x] 5. 实现错误处理机制 - 添加库加载失败的降级处理(多个 CDN 备选) - 实现代码解析错误的捕获和显示 - 创建错误提示 UI 组件 diff --git a/.kiro/steering/mermaid-removal-summary.md b/.kiro/steering/mermaid-removal-summary.md new file mode 100644 index 0000000..ca4b121 --- /dev/null +++ b/.kiro/steering/mermaid-removal-summary.md @@ -0,0 +1,51 @@ +--- +inclusion: manual +--- + +# Mermaid 功能移除总结 + +## 移除原因 + +WP-Markdown 编辑器在保存 Markdown 文件时,会将 Mermaid 代码块保存为一整行(没有真正的换行符),导致 Mermaid 解析器无法正确解析,持续报错:`Parse error on line 1: Expecting 'NEWLINE', 'SPACE', 'GRAPH'`。 + +尝试了多种解决方案(JavaScript 解码、PHP 预处理、智能格式化等)均失败。 + +## 已移除内容 + +1. **settings.php** - 移除 Mermaid 设置项和选项保存逻辑(约 15 行) +2. **functions.php** - 移除两个 Mermaid 处理函数(约 70 行) +3. **footer.php** - 移除 Mermaid 加载和渲染代码(约 220 行) +4. **style.css** - 移除 Mermaid 图表样式(约 25 行) +5. **本地镜像** - 删除 `assets/vendor/external/mermaid/` 目录 + +总计移除约 330 行代码和 2 个本地镜像文件。 + +## 需求文档 + +已创建 `mermaid-support-requirements.md` 文档,包含: +- 问题详细分析 +- 已尝试的解决方案 +- 推荐的替代方案(使用支持 Mermaid 的插件) +- 技术实现参考 +- 测试用例 + +## 推荐方案 + +使用支持 Mermaid 的 WordPress 插件: +- **WP Githuber MD** - 功能强大的 Markdown 编辑器 +- **Markdown Block** - Gutenberg 原生 Markdown 块 +- **Code Syntax Block** - 支持 Mermaid 的代码块插件 + +## Git 提交 + +``` +commit 54cbb40 +feat: 移除 Mermaid 支持并创建需求文档 + +- 从 settings.php 移除 Mermaid 设置项和选项保存逻辑 +- 从 functions.php 移除 Mermaid 代码块预处理函数 +- 从 footer.php 移除 Mermaid 加载和渲染代码 +- 从 style.css 移除 Mermaid 图表样式 +- 删除本地镜像文件 assets/vendor/external/mermaid/ +- 创建 mermaid-support-requirements.md 需求文档 +``` diff --git a/argontheme.js b/argontheme.js index 5335ae4..8766224 100644 --- a/argontheme.js +++ b/argontheme.js @@ -4840,6 +4840,9 @@ void 0; // ---------- 启动渲染引擎 ---------- + // 暴露到全局作用域(用于库加载失败时的降级处理) + window.MermaidRenderer = MermaidRenderer; + // 在 DOM 加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { diff --git a/functions.php b/functions.php index 8679931..898ba8f 100644 --- a/functions.php +++ b/functions.php @@ -9340,6 +9340,168 @@ function argon_add_mermaid_async_attribute($tag, $handle) { return $tag; } - // 添加 async 属性 - return str_replace(' src', ' async src', $tag); + // 添加 async 属性和 onerror 事件处理 + $tag = str_replace(' src', ' async onerror="argonMermaidLoadFallback()" src', $tag); + + return $tag; } + +/** + * 添加 Mermaid 库加载失败的降级处理脚本 + */ +function argon_add_mermaid_fallback_script() { + // 只在启用 Mermaid 且页面包含 Mermaid 代码块时添加 + if (!argon_get_mermaid_option('enabled', false)) { + return; + } + + global $post; + $has_mermaid = false; + + if (is_singular() && isset($post->post_content)) { + $has_mermaid = argon_has_mermaid_content($post->post_content); + } + + if (!$has_mermaid) { + return; + } + + // 获取备用 CDN URL 列表 + $fallback_urls = argon_get_mermaid_fallback_urls(); + $fallback_urls_json = json_encode($fallback_urls); + + // 输出降级处理脚本 + ?> + + + + + + + Mermaid 库加载失败降级处理测试 + + + +

🧪 Mermaid 库加载失败降级处理测试

+ +
+

测试说明

+

本测试页面用于验证 Mermaid 库加载失败时的降级处理机制。

+
    +
  • 测试 1: 主 CDN 加载失败,自动尝试备用 CDN
  • +
  • 测试 2: 所有 CDN 都失败,显示友好的错误提示
  • +
  • 测试 3: 备用 CDN 加载成功,正常渲染图表
  • +
+
+ +
+

测试控制

+
+ + + + +
+
+
+ +
+

测试图表

+
+flowchart TD + A[开始] --> B{主 CDN 加载} + B -->|成功| C[渲染图表] + B -->|失败| D[尝试备用 CDN 1] + D -->|成功| C + D -->|失败| E[尝试备用 CDN 2] + E -->|成功| C + E -->|失败| F[显示错误提示] +
+
+ + + + diff --git a/tests/test-mermaid-fallback.php b/tests/test-mermaid-fallback.php new file mode 100644 index 0000000..6806a18 --- /dev/null +++ b/tests/test-mermaid-fallback.php @@ -0,0 +1,249 @@ + $url) { + if (empty($url)) { + echo "❌ 失败: URL #{$index} 为空\n"; + return false; + } + + // 验证 URL 格式 + if (!preg_match('/^https?:\/\/.+\.js$/', $url) && !preg_match('/\/mermaid\.min\.js$/', $url)) { + echo "❌ 失败: URL #{$index} 格式无效: {$url}\n"; + return false; + } + + echo "✓ URL #{$index}: {$url}\n"; + } + + echo "✅ 通过: 备用 CDN URL 列表验证成功\n\n"; + return true; +} + +/** + * 测试 2: 验证降级处理脚本生成 + */ +function test_fallback_script_generation() { + echo "测试 2: 验证降级处理脚本生成\n"; + echo str_repeat('-', 50) . "\n"; + + // 启用 Mermaid + update_option('argon_mermaid_enabled', true); + + // 创建一个包含 Mermaid 代码块的测试文章 + global $post; + $post = (object) [ + 'ID' => 1, + 'post_content' => '
flowchart TD\nA-->B
' + ]; + + // 捕获输出 + ob_start(); + argon_add_mermaid_fallback_script(); + $output = ob_get_clean(); + + // 验证输出包含必要的脚本 + $checks = [ + 'argonMermaidLoadFallback' => '降级处理函数', + 'loadMermaidWithFallback' => '递归加载函数', + 'showGlobalError' => '错误提示函数', + 'fallbackUrls' => '备用 URL 列表', + 'script.onerror' => '错误处理', + 'script.onload' => '加载成功处理' + ]; + + $all_passed = true; + foreach ($checks as $keyword => $description) { + if (strpos($output, $keyword) !== false) { + echo "✓ 包含 {$description}\n"; + } else { + echo "❌ 缺少 {$description}\n"; + $all_passed = false; + } + } + + if ($all_passed) { + echo "✅ 通过: 降级处理脚本生成正确\n\n"; + return true; + } else { + echo "❌ 失败: 降级处理脚本不完整\n\n"; + return false; + } +} + +/** + * 测试 3: 验证 onerror 属性添加 + */ +function test_onerror_attribute() { + echo "测试 3: 验证 onerror 属性添加\n"; + echo str_repeat('-', 50) . "\n"; + + // 模拟脚本标签 + $original_tag = ''; + + // 调用函数添加属性 + $modified_tag = argon_add_mermaid_async_attribute($original_tag, 'mermaid'); + + // 验证包含 async 属性 + if (strpos($modified_tag, 'async') === false) { + echo "❌ 失败: 缺少 async 属性\n"; + return false; + } + echo "✓ 包含 async 属性\n"; + + // 验证包含 onerror 属性 + if (strpos($modified_tag, 'onerror') === false) { + echo "❌ 失败: 缺少 onerror 属性\n"; + return false; + } + echo "✓ 包含 onerror 属性\n"; + + // 验证 onerror 调用正确的函数 + if (strpos($modified_tag, 'argonMermaidLoadFallback()') === false) { + echo "❌ 失败: onerror 函数名不正确\n"; + return false; + } + echo "✓ onerror 调用正确的函数\n"; + + echo "修改后的标签: {$modified_tag}\n"; + echo "✅ 通过: onerror 属性添加正确\n\n"; + return true; +} + +/** + * 测试 4: 验证非 Mermaid 脚本不受影响 + */ +function test_other_scripts_unaffected() { + echo "测试 4: 验证非 Mermaid 脚本不受影响\n"; + echo str_repeat('-', 50) . "\n"; + + // 模拟其他脚本标签 + $original_tag = ''; + + // 调用函数 + $modified_tag = argon_add_mermaid_async_attribute($original_tag, 'other-script'); + + // 验证标签未被修改 + if ($original_tag === $modified_tag) { + echo "✓ 非 Mermaid 脚本未被修改\n"; + echo "✅ 通过: 其他脚本不受影响\n\n"; + return true; + } else { + echo "❌ 失败: 非 Mermaid 脚本被错误修改\n"; + echo "原始: {$original_tag}\n"; + echo "修改: {$modified_tag}\n\n"; + return false; + } +} + +/** + * 测试 5: 验证 JSON 编码的备用 URL + */ +function test_json_encoded_urls() { + echo "测试 5: 验证 JSON 编码的备用 URL\n"; + echo str_repeat('-', 50) . "\n"; + + $fallback_urls = argon_get_mermaid_fallback_urls(); + $json = json_encode($fallback_urls); + + // 验证 JSON 编码成功 + if ($json === false) { + echo "❌ 失败: JSON 编码失败\n"; + return false; + } + echo "✓ JSON 编码成功\n"; + + // 验证可以解码回数组 + $decoded = json_decode($json, true); + if (!is_array($decoded)) { + echo "❌ 失败: JSON 解码失败\n"; + return false; + } + echo "✓ JSON 解码成功\n"; + + // 验证解码后的数组与原数组一致 + if ($decoded !== $fallback_urls) { + echo "❌ 失败: 解码后的数组与原数组不一致\n"; + return false; + } + echo "✓ 解码后的数组与原数组一致\n"; + + echo "JSON: {$json}\n"; + echo "✅ 通过: JSON 编码验证成功\n\n"; + return true; +} + +// 运行所有测试 +echo "\n"; +echo "=================================================\n"; +echo "Mermaid 库加载失败降级处理测试\n"; +echo "=================================================\n\n"; + +$tests = [ + 'test_fallback_urls', + 'test_fallback_script_generation', + 'test_onerror_attribute', + 'test_other_scripts_unaffected', + 'test_json_encoded_urls' +]; + +$passed = 0; +$failed = 0; + +foreach ($tests as $test) { + if ($test()) { + $passed++; + } else { + $failed++; + } +} + +// 输出测试总结 +echo "=================================================\n"; +echo "测试总结\n"; +echo "=================================================\n"; +echo "通过: {$passed} 个测试\n"; +echo "失败: {$failed} 个测试\n"; + +if ($failed === 0) { + echo "\n✅ 所有测试通过!\n"; + exit(0); +} else { + echo "\n❌ 部分测试失败,请检查实现。\n"; + exit(1); +}