Files
Lauren Hirata Singh e69a3328ed Lint files + add a PR labeler (#534)
## Overview
- make lint / make format
- added a PR labeler

## Type of change
**Type:**  Infra

## Related issues/PRs
<!-- Link to related issues, feature PRs, or discussions (if applicable)
-->
- GitHub issue:
- Feature PR:

<!-- For LangChain employees, if applicable: -->
- Linear issue:
- Slack thread:

## Checklist
<!-- Put an 'x' in all boxes that apply -->
- [x] I have read the [contributing guidelines](README.md)
- [x] I have tested my changes locally using `docs dev`
- [x] All code examples have been tested and work correctly
- [x] I have used **root relative** paths for internal links
- [x] I have updated navigation in `src/docs.json` if needed
- [x] I have gotten approval from the relevant reviewers
- [ ] (Internal team members only / optional) I have created a preview
deployment using the [Create Preview Branch
workflow](https://github.com/langchain-ai/docs/actions/workflows/create-preview-branch.yml)

## Additional notes
<!-- Any other information that would be helpful for reviewers -->
2025-09-15 11:18:50 -04:00

629 lines
13 KiB
Python

"""Tests for the markdown parser."""
import pytest
from pipeline.tools.parser import (
CodeBlock,
ConditionalBlock,
Document,
Heading,
Paragraph,
ParseError,
Parser,
to_mint,
)
def test_parse_simple_heading() -> None:
"""Test parsing a simple heading."""
text = "# Hello World"
parser = Parser(text)
doc = parser.parse()
assert isinstance(doc, Document)
assert len(doc.blocks) == 1
heading = doc.blocks[0]
assert isinstance(heading, Heading)
assert heading.level == 1
assert heading.value == "Hello World"
assert heading.start_line == 1
assert heading.limit_line == 2
def test_parse_simple_paragraph() -> None:
"""Test parsing a simple paragraph."""
text = "This is a simple paragraph."
parser = Parser(text)
doc = parser.parse()
assert isinstance(doc, Document)
assert len(doc.blocks) == 1
paragraph = doc.blocks[0]
assert isinstance(paragraph, Paragraph)
assert paragraph.value == ["This is a simple paragraph."]
assert paragraph.start_line == 1
assert paragraph.limit_line == 2
# This is a set of tests that verifies end-to-end functionality from parsing markdown
# to converting it to Mintlify format.
MARKDOWN1 = """\
??? example "Example Title"
Foldable example
"""
EXPECTED_MARKDOWN = """\
<Accordion title="Example Title">
Foldable example
</Accordion>
"""
def test_example_admonition() -> None:
"""Test the Mintlify printer."""
output = to_mint(MARKDOWN1)
assert output == EXPECTED_MARKDOWN
EXAMPLE_ADMONITION_BLANK = """\
??? example "Example Title"
Paragraph 1
Paragraph 2
"""
EXPECTED_ADMONITION_BLANK = """\
<Accordion title="Example Title">
Paragraph 1
Paragraph 2
</Accordion>
"""
def test_example_admonition_with_blank_line() -> None:
"""Test the Mintlify printer with a blank line after admonition."""
assert to_mint(EXAMPLE_ADMONITION_BLANK) == EXPECTED_ADMONITION_BLANK
INPUT_FRONT_MATTER = """\
---
title: Example Document
other_field: value
---
# Example Heading
This is a simple document with front matter.
"""
# We convert the first heading to a Mintlify front matter block.
EXPECTED_FRONT_MATTER = """\
---
title: Example Heading
---
This is a simple document with front matter.
"""
def test_front_matter_ignored_in_output() -> None:
"""Test that front matter is ignored in Mintlify output."""
# Let's test the AST first.
assert to_mint(INPUT_FRONT_MATTER) == EXPECTED_FRONT_MATTER
INPUT_WITH_TABS = """\
=== "Python"
Content with tabs
=== "javascript"
Other content with tabs
"""
EXPECTED_TABS = """\
<Tabs>
<Tab title="Python">
Content with tabs
</Tab>
<Tab title="javascript">
Other content with tabs
</Tab>
</Tabs>
"""
def test_tabs() -> None:
"""Test parsing with tabs."""
assert to_mint(INPUT_WITH_TABS) == EXPECTED_TABS
INPUT_WITH_CODE_BLOCK = """\
```python
print("Hello, World!")
```
"""
EXPECTED_CODE_BLOCK = """\
```python
print("Hello, World!")
```
"""
def test_code_block() -> None:
"""Test parsing a code block."""
assert to_mint(INPUT_WITH_CODE_BLOCK) == EXPECTED_CODE_BLOCK
INPUT_CODE_BLOCK_WITH_EXTRAS = """\
```python hl_lines="1-2"
print("Hello, World!")
print("This is a test.")
```
"""
EXPECTED_CODE_BLOCK_WITH_EXTRAS = """\
```python hl_lines="1-2"
print("Hello, World!")
print("This is a test.")
```
"""
def test_code_block_with_extras() -> None:
"""Test parsing a code block with extra lines."""
assert to_mint(INPUT_CODE_BLOCK_WITH_EXTRAS) == EXPECTED_CODE_BLOCK_WITH_EXTRAS
INPUT_WITH_UNORDERED_LIST = """\
- Item 1
- Item 2
- Item 3
"""
EXPECTED_UNORDERED_LIST = """\
* Item 1
* Item 2
* Item 3
"""
def test_unordered_list() -> None:
"""Test parsing an unordered list."""
assert to_mint(INPUT_WITH_UNORDERED_LIST) == EXPECTED_UNORDERED_LIST
INPUT_WITH_ORDERED_LIST = """\
1. First item
2. Second item
3. Third item
"""
EXPECTED_ORDERED_LIST = """\
1. First item
2. Second item
3. Third item
"""
def test_ordered_list() -> None:
"""Test parsing an ordered list."""
assert to_mint(INPUT_WITH_ORDERED_LIST) == EXPECTED_ORDERED_LIST
INPUT_WITH_BLOCKQUOTE = """\
> Example Blockquote
> More text in the blockquote.
"""
EXPECTED_BLOCKQUOTE = """\
> Example Blockquote
> More text in the blockquote.
"""
def test_blockquote() -> None:
"""Test parsing a blockquote."""
assert to_mint(INPUT_WITH_BLOCKQUOTE) == EXPECTED_BLOCKQUOTE
INPUT_WITH_HTML = """\
<div>
<p>This is a paragraph inside a div.</p>
</div>
"""
EXPECTED_HTML = """\
<div>
<p>This is a paragraph inside a div.</p>
</div>
"""
def test_html() -> None:
"""Test parsing HTML content."""
assert to_mint(INPUT_WITH_HTML) == EXPECTED_HTML
INPUT_CODE_BLOCK_IN_TAB = """\
=== "Python"
```python
def hello_world():
print("Hello, World!")
```
"""
EXPECTED_CODE_BLOCK_IN_TAB = """\
<Tabs>
<Tab title="Python">
```python
def hello_world():
print("Hello, World!")
```
</Tab>
</Tabs>
"""
def test_code_block_in_tab() -> None:
"""Test parsing a code block inside a tab."""
assert to_mint(INPUT_CODE_BLOCK_IN_TAB) == EXPECTED_CODE_BLOCK_IN_TAB
INPUT_CODE_FENCE = """\
```
def example_function():
print("This is an example function.")
```
"""
EXPECTED_CODE_FENCE = """\
```
def example_function():
print("This is an example function.")
```
"""
def test_code_fence() -> None:
"""Test parsing a code fence."""
assert to_mint(INPUT_CODE_FENCE) == EXPECTED_CODE_FENCE
INPUT_CODE_BLOCK_WITH_BLANK_LINE = """\
```python
def foo():
x = 1
y = 2
```
"""
def test_long_code_block() -> None:
"""Test parsing a long code block."""
ast = Parser(INPUT_CODE_BLOCK_WITH_BLANK_LINE).parse()
first_block = ast.blocks[0]
assert isinstance(first_block, CodeBlock)
assert first_block.language == "python"
assert first_block.content == "def foo():\n x = 1\n\n y = 2"
NOTE_WITH_TITLE = """\
!!! info "Requirements"
content
"""
EXPECTED_NOTE_WITH_TITLE = """\
<Info>
**Requirements**
content
</Info>
"""
def test_note_with_title() -> None:
"""Test parsing a note with a title."""
assert to_mint(NOTE_WITH_TITLE) == EXPECTED_NOTE_WITH_TITLE
INPUT_TABLE = """\
| Property | Description |
| -------- | ------------------------------------- |
| Name | Full name of user |
| Age | Reported age |
| Joined | Whether the user joined the community |
"""
def test_table() -> None:
"""Test parsing a table."""
assert to_mint(INPUT_TABLE) == INPUT_TABLE
INDENTED_BLOCK = """\
This block has indentation:
{
"key": "value"
}
"""
def test_parse_conditional_block_python() -> None:
"""Test parsing a Python conditional block."""
text = """\
:::python
print("Hello, World!")
:::"""
parser = Parser(text)
doc = parser.parse()
assert isinstance(doc, Document)
assert len(doc.blocks) == 1
block = doc.blocks[0]
assert isinstance(block, ConditionalBlock)
assert block.language == "python"
assert block.start_line == 1
assert block.limit_line == 4
assert len(block.blocks) == 1
inner_block = block.blocks[0]
assert isinstance(inner_block, Paragraph)
assert inner_block.value == ['print("Hello, World!")']
def test_parse_conditional_block_js() -> None:
"""Test parsing a JavaScript conditional block."""
text = """\
:::js
console.log("Hello, World!");
:::"""
parser = Parser(text)
doc = parser.parse()
assert isinstance(doc, Document)
assert len(doc.blocks) == 1
block = doc.blocks[0]
assert isinstance(block, ConditionalBlock)
assert block.language == "js"
assert len(block.blocks) == 1
inner_block = block.blocks[0]
assert isinstance(inner_block, Paragraph)
assert inner_block.value == ['console.log("Hello, World!");']
def test_parse_conditional_block_with_multiple_paragraphs() -> None:
"""Test parsing a conditional block with multiple paragraphs."""
text = """\
:::python
print("First line")
print("Second line")
:::"""
parser = Parser(text)
doc = parser.parse()
assert isinstance(doc, Document)
assert len(doc.blocks) == 1
block = doc.blocks[0]
assert isinstance(block, ConditionalBlock)
assert block.language == "python"
assert len(block.blocks) == 2
first_paragraph = block.blocks[0]
assert isinstance(first_paragraph, Paragraph)
assert first_paragraph.value == ['print("First line")']
second_paragraph = block.blocks[1]
assert isinstance(second_paragraph, Paragraph)
assert second_paragraph.value == ['print("Second line")']
def test_parse_indented_conditional_block() -> None:
"""Test parsing an indented conditional block."""
text = """\
:::js
console.log("Indented");
:::"""
parser = Parser(text)
doc = parser.parse()
assert isinstance(doc, Document)
assert len(doc.blocks) == 1
block = doc.blocks[0]
assert isinstance(block, ConditionalBlock)
assert block.language == "js"
assert block.indent == 4
assert len(block.blocks) == 1
INPUT_CONDITIONAL_PYTHON = """\
:::python
```python
def hello():
print("Hello from Python!")
hello()
```
:::"""
def test_conditional_block_python_to_mint() -> None:
"""Test converting a Python conditional block to Mintlify format."""
assert to_mint(INPUT_CONDITIONAL_PYTHON) == INPUT_CONDITIONAL_PYTHON + "\n"
INPUT_CONDITIONAL_JS = """\
:::js
```js
function hello() {
console.log("Hello from JavaScript!");
}
hello();
```
:::"""
def test_conditional_block_js_to_mint() -> None:
"""Test converting a JavaScript conditional block to Mintlify format."""
assert to_mint(INPUT_CONDITIONAL_JS) == INPUT_CONDITIONAL_JS + "\n"
INPUT_INDENTED_CONDITIONAL = """\
hello
=== "Tab1"
:::python
```python
print("Indented conditional block")
```
:::
"""
EXPECTED_INDENTED_CONDITIONAL = """\
hello
<Tabs>
<Tab title="Tab1">
:::python
```python
print("Indented conditional block")
```
:::
</Tab>
</Tabs>
"""
def test_indented_conditional_block_to_mint() -> None:
"""Test converting an indented conditional block to Mintlify format."""
assert to_mint(INPUT_INDENTED_CONDITIONAL) == EXPECTED_INDENTED_CONDITIONAL
def test_conditional_block_with_whitespace_before_closing_tag() -> None:
"""Test parsing conditional block with extra whitespace before closing :::."""
# This test reproduces the infinite loop bug where whitespace before
# the closing ::: tag causes the parser to get stuck
text = """\
:::python
some text here
1. blah
2. moove
:::"""
parser = Parser(text)
doc = parser.parse()
assert isinstance(doc, Document)
assert len(doc.blocks) == 1
block = doc.blocks[0]
assert isinstance(block, ConditionalBlock)
assert block.language == "python"
assert len(block.blocks) == 2 # "some text here", ordered list with 2 items
def test_admonition_with_conditional_block_close() -> None:
"""Test admonition with orphaned conditional block closing tag raises ParseError."""
text = """\
!!! note "Title"
Some content here
:::"""
parser = Parser(text)
with pytest.raises(ParseError) as exc_info:
parser.parse()
assert "conditional block close" in str(exc_info.value)
assert exc_info.value.line == 3
assert ":::" in str(exc_info.value)
def test_tab_block_with_conditional_block_close() -> None:
"""Test tab block with orphaned conditional block closing tag raises ParseError."""
text = """\
=== "Tab Title"
Some tab content
:::"""
parser = Parser(text)
with pytest.raises(ParseError) as exc_info:
parser.parse()
assert "conditional block close" in str(exc_info.value)
assert exc_info.value.line == 3
assert ":::" in str(exc_info.value)
def test_admonition_with_fence_token() -> None:
"""Test admonition containing orphaned fence token raises ParseError."""
text = """\
!!! warning "Important"
Some warning text
```"""
parser = Parser(text)
with pytest.raises(ParseError) as exc_info:
parser.parse()
assert "fence" in str(exc_info.value)
assert exc_info.value.line == 3
assert "```" in str(exc_info.value)
def test_tab_block_with_fence_token() -> None:
"""Test tab block containing orphaned fence token raises ParseError."""
text = """\
=== "Code Tab"
Here's some code:
```"""
parser = Parser(text)
with pytest.raises(ParseError) as exc_info:
parser.parse()
assert "fence" in str(exc_info.value)
def test_nested_list_with_conditional_close() -> None:
"""Test nested list with orphaned conditional block close raises ParseError."""
text = """\
1. First item
- Nested item
:::"""
parser = Parser(text)
with pytest.raises(ParseError) as exc_info:
parser.parse()
assert "conditional block close" in str(exc_info.value)
assert exc_info.value.line == 3
assert ":::" in str(exc_info.value)
def test_admonition_with_front_matter_token() -> None:
"""Test admonition containing orphaned front matter token raises ParseError."""
text = """\
!!! info "Information"
Some info here
---"""
parser = Parser(text)
with pytest.raises(ParseError) as exc_info:
parser.parse()
assert "front matter" in str(exc_info.value)
assert exc_info.value.line == 3
assert "---" in str(exc_info.value)