e0a5f57793
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as a monorepo subdirectory under desktop/. Source only — node_modules, dist, Cargo target and the upstream .git history are excluded. desktop/ keeps its own independent Cargo workspace (desktop/src-tauri with crates/*); it is not part of the backend boosapi crate, so a `cargo check` at the metapi root still compiles only the backend. Build the desktop app from desktop/src-tauri per desktop/README.md. Note: app icons are still placeholder assets pending the final BoosAPI logo. The updater signing keys live outside the repo and are not included.
443 lines
14 KiB
TypeScript
443 lines
14 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { getMarkdown, parseMarkdownToStructure } from '../packages/markdown-parser/src/index'
|
|
|
|
describe('indented code block to paragraph conversion', () => {
|
|
const md = getMarkdown()
|
|
|
|
it('should convert single-line indented text to paragraph', () => {
|
|
const markdown = ` This is just indented text`
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
|
|
expect(result.length).toBe(1)
|
|
expect(result[0].type).toBe('paragraph')
|
|
expect(result[0].children?.[0].type).toBe('text')
|
|
expect(result[0].children?.[0].content).toBe('This is just indented text')
|
|
})
|
|
|
|
it('should keep multi-line indented code as code_block', () => {
|
|
const markdown = ` line one\n line two\n line three`
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
|
|
expect(result.length).toBe(1)
|
|
expect(result[0].type).toBe('code_block')
|
|
})
|
|
|
|
it('should keep single-line code-like patterns as code_block', () => {
|
|
const testCases = [
|
|
// Original cases
|
|
` const x = 1`,
|
|
` function foo() {}`,
|
|
` if (true) return`,
|
|
` https://example.com`,
|
|
` ['a', 'b', 'c']`,
|
|
|
|
// Function/method calls with parentheses
|
|
` foo()`,
|
|
` obj.method()`,
|
|
` array.push(item)`,
|
|
` $.ajax()`,
|
|
` _underscore.method()`,
|
|
|
|
// Property access and chaining
|
|
` obj.prop`,
|
|
` arr[0]`,
|
|
` data.items[0].name`,
|
|
` obj?.prop`,
|
|
` this.property`,
|
|
|
|
// Comparison and logical operators
|
|
` a + b`,
|
|
` x > y`,
|
|
` count !== 0`,
|
|
` value === null`,
|
|
` flag && true`,
|
|
` result || default`,
|
|
|
|
// Assignment operators
|
|
` x = 1`,
|
|
` y += 2`,
|
|
` z *= 3`,
|
|
|
|
// JSX/TSX tags
|
|
` <Component />`,
|
|
` <div>content</div>`,
|
|
|
|
// Template strings
|
|
` \`hello ${name}\``,
|
|
|
|
// Arrow functions
|
|
` () => {}`,
|
|
` x => x * 2`,
|
|
|
|
// Decorators
|
|
` @Component`,
|
|
|
|
// Numbers with units
|
|
` 100px`,
|
|
` 1.5rem`,
|
|
` 0xFF`,
|
|
|
|
// Console methods
|
|
` console.log('test')`,
|
|
` document.getElementById('id')`,
|
|
|
|
// Command-like patterns
|
|
` git status`,
|
|
` npm install`,
|
|
` docker build`,
|
|
|
|
// Comment patterns
|
|
` // comment`,
|
|
` # comment`,
|
|
|
|
// Heredoc patterns
|
|
` <<EOF`,
|
|
]
|
|
|
|
for (const markdown of testCases) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block')
|
|
}
|
|
})
|
|
|
|
it('should handle the original streamContent case correctly', () => {
|
|
const streamContent = `
|
|
<hide>uid:b238ea9cb1a44ae489247aed335a0248</hide>
|
|
|
|
<TaskPlan>
|
|
已收到您关于xxx的组织机构画像的任务请求,我将按照以下步骤执行该任务:
|
|
- 匹配并确认目标组织机构
|
|
- 构建组织机构画像管理数据
|
|
</TaskPlan>
|
|
|
|
<AnalysisSteps>
|
|
# 匹配信息和确认组织机构
|
|
## 深度思考
|
|
-
|
|
- 好的,我现在需要处理用户的输入:"生成x的织机构画像"。首先,我需要确定用户是否提到了组织机构的名称。用户提到的"x"看起来像是一个公司名称,但可能存在拼写错误,比如"织机构"应该是"组织机构"。不过,这里的关键是提取组织机构名称。根据规则,我需要检查用户输入中的组织名称,并去除所有后缀,比如"集团、公司、股份有限公司"等。但"x"这个名称中没有明显的后缀,比如"公司"或"集团"之类的词。不过,可能用户输入有误,比如"织机构"可能是"组织机构"的笔误,但这里的问题是关于提取组织名称。接下来,我需要确认"x"是否是一个有效的组织机构名称。x是印度的一家电信公司,全称可能是x,但用户输入中没有提到"Limited"或类似的后缀。因此,根据规则,如果名称中没有后缀,就保留原始名称。所以,这里应该提取"x"作为品牌名称。另外,需要检查是否有地域或业务修饰词。例如,如果名称中有"(深圳)"或"科技"之类的词,需要保留业务关键词,但这里没有。因此,直接保留"x"。还要确保没有其他可能的后缀被遗漏。例如,如果用户输入的是"x",那么需要去掉"公司"得到"x"。但当前输入中没有这样的后缀,所以直接保留。最后,检查是否符合返回"unknow"的条件。用户输入中包含了一个可能的组织名称,且去除后缀后是有效的品牌名称,因此不应返回"unknow"。
|
|
## 多维度匹配组织机构管理数据
|
|
- 调用数据接口,机构画像关联数据匹配中......
|
|
- 该机构没有关联数据匹配。
|
|
</AnalysisSteps>
|
|
|
|
✖ 该机构没有关联机构数据匹配,无法进行机构画像生成。
|
|
`
|
|
const result = parseMarkdownToStructure(streamContent, md, { final: true })
|
|
|
|
expect(result.length).toBe(4)
|
|
// First node: paragraph containing html_block <hide>
|
|
expect(result[0].type).toBe('paragraph')
|
|
expect(result[0].children?.[0].type).toBe('html_block')
|
|
|
|
// Second node: html_block <TaskPlan>
|
|
expect(result[1].type).toBe('html_block')
|
|
expect((result[1] as any).tag).toBe('taskplan')
|
|
|
|
// Third node: html_block <AnalysisSteps>
|
|
expect(result[2].type).toBe('html_block')
|
|
expect((result[2] as any).tag).toBe('analysissteps')
|
|
|
|
// Fourth node: paragraph (not code_block!)
|
|
expect(result[3].type).toBe('paragraph')
|
|
expect(result[3].children?.[0].type).toBe('text')
|
|
expect(result[3].children?.[0].content).toBe('✖ 该机构没有关联机构数据匹配,无法进行机构画像生成。')
|
|
})
|
|
|
|
describe('edge cases for looksLikeCode function', () => {
|
|
it('should convert plain indented text with special characters to paragraph', () => {
|
|
const testCases = [
|
|
` Just a sentence.`,
|
|
` Hello, world!`,
|
|
` This is text...`,
|
|
` What about this?`,
|
|
// Note: "Price: $99.99" has `:` which is treated as code-like
|
|
// Note: "50% off" has `%` which is treated as code-like
|
|
` Hello world`,
|
|
` Test message`,
|
|
` Regular text here`,
|
|
// Note: "# Not a heading..." has `#` which is treated as code (shell comment)
|
|
` - Not a list because indented`,
|
|
` * Not a list because indented`,
|
|
]
|
|
|
|
for (const markdown of testCases) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('paragraph', `Expected paragraph for: ${markdown}`)
|
|
}
|
|
})
|
|
|
|
it('should keep HTML entity patterns as paragraph (not code)', () => {
|
|
const testCases = [
|
|
` `,
|
|
` &`,
|
|
` © 2024`,
|
|
` ✖ Error message`,
|
|
` ✔ Success`,
|
|
` <tag>`,
|
|
` "quoted"`,
|
|
` — dash`,
|
|
` 〹`,
|
|
` ∅`,
|
|
]
|
|
|
|
for (const markdown of testCases) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('paragraph', `Expected paragraph for: ${markdown}`)
|
|
}
|
|
})
|
|
|
|
it('should keep property access patterns as code_block', () => {
|
|
const testCases = [
|
|
` foo.bar`,
|
|
` obj.prop.value`,
|
|
` array[0].name`,
|
|
` data.items[index]`,
|
|
` object?.optional`,
|
|
` this.property`,
|
|
` module.exports`,
|
|
` Math.PI`,
|
|
` document.body`,
|
|
` window.location`,
|
|
]
|
|
|
|
for (const markdown of testCases) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block', `Expected code_block for: ${markdown}`)
|
|
}
|
|
})
|
|
|
|
it('should keep code-like expressions as code_block', () => {
|
|
const testCases = [
|
|
` a + b`,
|
|
` x * y`,
|
|
` result || fallback`,
|
|
` flag && true`,
|
|
` count > 0`,
|
|
` value === null`,
|
|
` i++`,
|
|
` arr.length`,
|
|
` str.length`,
|
|
` !!value`,
|
|
` x ? y : z`,
|
|
` count != 0`,
|
|
` a <= b`,
|
|
]
|
|
|
|
for (const markdown of testCases) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block', `Expected code_block for: ${markdown}`)
|
|
}
|
|
})
|
|
|
|
it('should keep quoted strings with trailing punctuation as code_block', () => {
|
|
const testCases = [
|
|
` "hello";`,
|
|
` 'world'`,
|
|
` \`test\``,
|
|
` "string",`,
|
|
` 'value';`,
|
|
]
|
|
|
|
for (const markdown of testCases) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block', `Expected code_block for: ${markdown}`)
|
|
}
|
|
})
|
|
|
|
it('should keep JSX/TSX patterns as code_block', () => {
|
|
const testCases = [
|
|
` <Component />`,
|
|
` <div>content</div>`,
|
|
` <span className="test">`,
|
|
` <Button onClick={handler}>`,
|
|
` <input type="text" />`,
|
|
` <CustomComponent prop="value">`,
|
|
]
|
|
|
|
for (const markdown of testCases) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block', `Expected code_block for: ${markdown}`)
|
|
}
|
|
})
|
|
|
|
it('should handle mixed punctuation correctly', () => {
|
|
const shouldBeParagraphs = [
|
|
` ...`,
|
|
` ---`,
|
|
` ***`,
|
|
` +++`,
|
|
` ===`,
|
|
]
|
|
|
|
for (const markdown of shouldBeParagraphs) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('paragraph', `Expected paragraph for: ${markdown}`)
|
|
}
|
|
})
|
|
|
|
it('should handle numeric values correctly', () => {
|
|
const shouldBeCode = [
|
|
` 42`,
|
|
` 3.14`,
|
|
` 0xFF`,
|
|
` 0b1010`,
|
|
` 100px`,
|
|
` 1.5rem`,
|
|
` 50%`,
|
|
` 90deg`,
|
|
` 500ms`,
|
|
]
|
|
|
|
for (const markdown of shouldBeCode) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block', `Expected code_block for: ${markdown}`)
|
|
}
|
|
})
|
|
|
|
it('should handle email and URL patterns correctly', () => {
|
|
// URLs should be code
|
|
const shouldBeCode = [
|
|
` https://example.com`,
|
|
` http://test.com`,
|
|
` www.example.com`,
|
|
` //cdn.example.com`,
|
|
` ftp://files.example.com`,
|
|
]
|
|
|
|
for (const markdown of shouldBeCode) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block')
|
|
}
|
|
|
|
// Email-like - @ symbol without decorator pattern should be paragraph
|
|
// user@example.com has @ but doesn't match ^@\w+$ pattern
|
|
const shouldBeCodeEmails = [
|
|
` user@example.com`,
|
|
` admin@test.org`,
|
|
]
|
|
|
|
for (const markdown of shouldBeCodeEmails) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
// user@example.com has . which makes it look like property access (code)
|
|
// So we expect code_block, not paragraph
|
|
expect(result[0].type).toBe('code_block')
|
|
}
|
|
})
|
|
|
|
it('should handle edge cases with operators and symbols', () => {
|
|
const shouldBeCode = [
|
|
` =>`,
|
|
` ->`,
|
|
` ::`,
|
|
` @Component`,
|
|
` #! /bin/bash`,
|
|
` <!-- comment`,
|
|
]
|
|
|
|
for (const markdown of shouldBeCode) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block')
|
|
}
|
|
})
|
|
|
|
it('should handle command-like patterns as code', () => {
|
|
const testCases = [
|
|
` git status`,
|
|
` npm install`,
|
|
` yarn add`,
|
|
` pip install`,
|
|
` cargo build`,
|
|
` docker run`,
|
|
` kubectl get pods`,
|
|
` python script.py`,
|
|
` node server.js`,
|
|
]
|
|
|
|
for (const markdown of testCases) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block')
|
|
}
|
|
})
|
|
|
|
it('should handle whitespace-only content', () => {
|
|
const testCases = [
|
|
` `,
|
|
` `,
|
|
` \t`,
|
|
` \n`,
|
|
]
|
|
|
|
for (const markdown of testCases) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
// Empty/whitespace content should not create a paragraph node
|
|
// or may create an empty paragraph - either is acceptable
|
|
expect(result.length).toBeGreaterThanOrEqual(0)
|
|
}
|
|
})
|
|
|
|
it('should handle Chinese text with punctuation', () => {
|
|
const shouldBeParagraphs = [
|
|
` 你好,世界!`,
|
|
` 这是一个测试。`,
|
|
` 价格:99元`,
|
|
` 完成率:50%`,
|
|
` 第1步`,
|
|
` - 不是列表`,
|
|
]
|
|
|
|
for (const markdown of shouldBeParagraphs) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('paragraph')
|
|
}
|
|
})
|
|
|
|
it('should handle bracket/brace patterns', () => {
|
|
const shouldBeCode = [
|
|
` []`,
|
|
` {}`,
|
|
` [1, 2, 3]`,
|
|
` { key: 'value' }`,
|
|
` ()`,
|
|
` (x) => x`,
|
|
` <div>`,
|
|
` </div>`,
|
|
]
|
|
|
|
for (const markdown of shouldBeCode) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block')
|
|
}
|
|
})
|
|
|
|
it('should handle boolean and null-like patterns', () => {
|
|
const shouldBeCode = [
|
|
` true`,
|
|
` false`,
|
|
` null`,
|
|
` undefined`,
|
|
` NaN`,
|
|
` Infinity`,
|
|
]
|
|
|
|
for (const markdown of shouldBeCode) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block')
|
|
}
|
|
})
|
|
|
|
it('should handle heredoc patterns', () => {
|
|
const shouldBeCode = [
|
|
` <<EOF`,
|
|
` << 'EOF'`,
|
|
` <<EOF`,
|
|
` <<<HTML`,
|
|
]
|
|
|
|
for (const markdown of shouldBeCode) {
|
|
const result = parseMarkdownToStructure(markdown, md, { final: true })
|
|
expect(result[0].type).toBe('code_block')
|
|
}
|
|
})
|
|
})
|
|
})
|