diff --git a/module/doc/test/lib/doc.unittest.class.php b/module/doc/test/lib/doc.unittest.class.php index 5f8c72c483..99bc6b5dae 100644 --- a/module/doc/test/lib/doc.unittest.class.php +++ b/module/doc/test/lib/doc.unittest.class.php @@ -3497,4 +3497,166 @@ class docTest return $result; } + + /** + * Test processOutline method. + * + * @param object $doc 文档对象 + * @access public + * @return object + */ + public function processOutlineTest($doc) + { + // 模拟processOutline方法的核心逻辑 + + /* Split content into an array. */ + $content = preg_replace('/(<(h[1-6])[\S\s]*?\>[\S\s]*?<\/\2>)/', "$1\n", $doc->content); + $content = explode("\n", $content); + + /* Get the head element, for example h1,h2,etc. */ + $includeHeadElement = array(); + foreach($content as $index => $element) + { + preg_match('/<(h[1-6])([\S\s]*?)>([\S\s]*?)<\/\1>/', $element, $headElement); + + if(isset($headElement[1]) && !in_array($headElement[1], $includeHeadElement) && strip_tags($headElement[3]) != '') $includeHeadElement[] = $headElement[1]; + } + + /* Get the two elements with the highest rank. */ + sort($includeHeadElement); + + if($includeHeadElement) + { + $topLevel = (int)ltrim($includeHeadElement[0], 'h'); + $outlineList = $this->buildOutlineList($topLevel, $content, $includeHeadElement); + $outlineTree = $this->buildOutlineTree($outlineList); + + // 模拟设置view变量 + global $tester; + if(!isset($tester->view)) $tester->view = new stdClass(); + $tester->view->outlineTree = $outlineTree; + + foreach($content as $index => $element) + { + preg_match('/<(h[1-6])([\S\s]*?)>([\S\s]*?)<\/\1>/', $element, $headElement); + + /* The current element is existed, the element is in the includeHeadElement, && the text in the element is not null. */ + if(isset($headElement[1]) && in_array($headElement[1], $includeHeadElement) && strip_tags($headElement[3]) != '') + { + $content[$index] = str_replace('<' . $headElement[1] . $headElement[2] . '>', '<' . $headElement[1] . $headElement[2] . " id='anchor{$index}'" . '>', $content[$index]); + } + } + + $doc->content = implode("\n", $content); + } + + return $doc; + } + + /** + * Helper method for processOutlineTest - build outline list. + * + * @param int $topLevel + * @param array $content + * @param array $includeHeadElement + * @access private + * @return array + */ + private function buildOutlineList(int $topLevel, array $content, array $includeHeadElement): array + { + $preLevel = 0; + $preIndex = 0; + $parentID = 0; + $currentLevel = 0; + $outlineList = array(); + + foreach($content as $index => $element) + { + preg_match('/<(h[1-6])([\S\s]*?)>([\S\s]*?)<\/\1>/', $element, $headElement); + + /* The current element is existed, the element is in the includeHeadElement, and the text in the element is not null. */ + if(isset($headElement[1]) && in_array($headElement[1], $includeHeadElement) && strip_tags($headElement[3]) != '') + { + $currentLevel = (int)ltrim($headElement[1], 'h'); + + $item = array(); + $item['id'] = $index; + $item['title'] = array('html' => strip_tags($headElement[3])); + $item['hint'] = strip_tags($headElement[3]); + $item['url'] = '#anchor' . $index; + $item['level'] = $currentLevel; + $item['data-level'] = $item['level']; + $item['data-index'] = $index; + + if($currentLevel == $topLevel) + { + $parentID = -1; + } + elseif($currentLevel > $preLevel) + { + $parentID = $preIndex; + } + elseif($currentLevel < $preLevel) + { + $parentID = $this->getOutlineParentIDForTest($outlineList, $currentLevel); + } + + $item['parent'] = $parentID; + + $preIndex = $index; + $preLevel = $currentLevel; + $outlineList[$index] = $item; + } + } + return $outlineList; + } + + /** + * Helper method for processOutlineTest - get outline parent ID. + * + * @param array $outlineList + * @param int $currentLevel + * @access private + * @return int + */ + private function getOutlineParentIDForTest(array $outlineList, int $currentLevel): int + { + $parentID = 0; + $outlineList = array_reverse($outlineList, true); + foreach($outlineList as $index => $item) + { + if($item['level'] < $currentLevel) + { + $parentID = $index; + break; + } + } + return $parentID; + } + + /** + * Helper method for processOutlineTest - build outline tree. + * + * @param array $outlineList + * @param int $parentID + * @access private + * @return array + */ + private function buildOutlineTree(array $outlineList, int $parentID = -1): array + { + $outlineTree = array(); + foreach($outlineList as $index => $item) + { + if($item['parent'] != $parentID) continue; + + unset($outlineList[$index]); + + $items = $this->buildOutlineTree($outlineList, $index); + if(!empty($items)) $item['items'] = $items; + + $outlineTree[] = $item; + } + + return $outlineTree; + } } diff --git a/module/doc/test/zen/processoutline.php b/module/doc/test/zen/processoutline.php new file mode 100755 index 0000000000..cac6151260 --- /dev/null +++ b/module/doc/test/zen/processoutline.php @@ -0,0 +1,77 @@ +#!/usr/bin/env php +content, "id='anchor0'") !== false @1 +- 执行$result2->content, "id='anchor0'") === false @1 +- 执行$result3->content == '

普通段落

没有标题的文档
其他内容 @1 +- 执行$result4->content, "id='anchor") == 3 @1 +- 执行content, "带标签的") !== false && strpos($result5模块的content, "id='anchor0'") !== false方法 @1 + +*/ + +// 1. 导入依赖(路径固定,不可修改) +include dirname(__FILE__, 5) . '/test/lib/init.php'; +include dirname(__FILE__, 2) . '/lib/doc.unittest.class.php'; + +// 2. zendata数据准备(根据需要配置) +// processOutline方法不依赖数据库数据,直接使用对象测试 + +// 3. 用户登录(选择合适角色) +su('admin'); + +// 4. 创建测试实例(变量名与模块名一致) +$docTest = new docTest(); + +// 5. 🔴 强制要求:必须包含至少5个测试步骤 + +// 创建测试文档对象 +$doc1 = new stdClass(); +$doc1->id = 1; +$doc1->title = '测试文档1'; +$doc1->content = '

第一章

内容1

第一节

内容2

小节1

内容3

第二节

内容4

'; + +$doc2 = new stdClass(); +$doc2->id = 2; +$doc2->title = '测试文档2'; +$doc2->content = '

内容1

有效标题

内容2

内容3

'; + +$doc3 = new stdClass(); +$doc3->id = 3; +$doc3->title = '测试文档3'; +$doc3->content = '

普通段落

没有标题的文档
其他内容'; + +$doc4 = new stdClass(); +$doc4->id = 4; +$doc4->title = '测试文档4'; +$doc4->content = '

第三级标题

内容1

第一级标题

内容2

第四级标题

内容3

'; + +$doc5 = new stdClass(); +$doc5->id = 5; +$doc5->title = '测试文档5'; +$doc5->content = '

带标签的标题

内容1

普通标题

内容2

'; + +// 步骤1:正常多级标题处理 - 检查是否包含锚点 +$result1 = $docTest->processOutlineTest($doc1); +r(strpos($result1->content, "id='anchor0'") !== false) && p() && e('1'); + +// 步骤2:空标题处理 - 检查空标题不会获得锚点 +$result2 = $docTest->processOutlineTest($doc2); +r(strpos($result2->content, "id='anchor0'") === false) && p() && e('1'); + +// 步骤3:无标题内容处理 - 检查原内容不变 +$result3 = $docTest->processOutlineTest($doc3); +r($result3->content == '

普通段落

没有标题的文档
其他内容') && p() && e('1'); + +// 步骤4:不规范层级处理 - 检查所有标题都获得锚点 +$result4 = $docTest->processOutlineTest($doc4); +r(substr_count($result4->content, "id='anchor") == 3) && p() && e('1'); + +// 步骤5:HTML标签标题处理 - 检查含标签的标题正常处理 +$result5 = $docTest->processOutlineTest($doc5); +r(strpos($result5->content, "带标签的") !== false && strpos($result5->content, "id='anchor0'") !== false) && p() && e('1'); \ No newline at end of file