Test/blocknote to markdown unit tests#1533
Conversation
|
@Glodykajabika is attempting to deploy a commit to the Rohan Verma's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR implements a complete chunk character span system for precise source line citations. The backend adds ChangesEnd-to-end chunk character span and line-citation feature
Sequence Diagram(s)sequenceDiagram
participant Agent
participant search_knowledge_base
participant DB
participant kb_postgres._load_file_data
participant numbered_document
participant LLM
Agent->>search_knowledge_base: query
search_knowledge_base->>DB: hybrid_search(query)
DB-->>search_knowledge_base: hits with start_char/end_char per chunk
search_knowledge_base->>DB: _resolve_doc_context(doc_ids)
DB-->>search_knowledge_base: document bodies & metadata
search_knowledge_base->>search_knowledge_base: render matched passages + compute line ranges
search_knowledge_base-->>Agent: formatted KB results with [citation:dID#Lx-y]
Agent->>kb_postgres._load_file_data: read_file(path, offset=0, matched_chunk_ids={101,102})
kb_postgres._load_file_data->>DB: fetch source_markdown + chunk spans
kb_postgres._load_file_data->>numbered_document: compute_matched_line_ranges(chunks, {101,102})
numbered_document-->>kb_postgres._load_file_data: [(5,8), (12,15)]
kb_postgres._load_file_data->>numbered_document: build_read_preamble(doc_id=42, ranges)
numbered_document-->>kb_postgres._load_file_data: XML preamble with metadata
kb_postgres._load_file_data-->>Agent: (file_data, 42, preamble)
Agent->>LLM: [preamble] + numbered source markdown
LLM-->>Agent: response citing [citation:d42#L5-8]
Estimated code review effort🎯 4 (Complex) | ⏱️ ~90 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
surfsense_backend/tests/integration/indexing_pipeline/test_index_spans.py (1)
32-37: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert span ordering/contiguity explicitly in the integration invariant helper.
Current checks can miss mispositioned offsets when chunk text repeats. Add a cursor-based monotonic/contiguous assertion to validate absolute positions, not just content equality.
🔧 Suggested strengthening
def _assert_spans_address_body(chunks: list[Chunk], body: str) -> None: + cursor = 0 for chunk in chunks: assert chunk.start_char is not None and chunk.end_char is not None + assert chunk.start_char == cursor + assert chunk.end_char >= chunk.start_char assert body[chunk.start_char : chunk.end_char] == chunk.content + cursor = chunk.end_char assert "".join(c.content for c in chunks) == body + assert cursor == len(body)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@surfsense_backend/tests/integration/indexing_pipeline/test_index_spans.py` around lines 32 - 37, The _assert_spans_address_body function needs to add explicit validation for chunk ordering and contiguity to catch mispositioned offsets. Add assertions that verify: chunks are ordered with monotonically increasing start_char positions, each chunk is contiguous with the previous one (each chunk's start_char equals the previous chunk's end_char), the first chunk begins at position 0, and the final chunk ends at the length of the body. Use a cursor variable that tracks the expected position of each successive chunk to validate these invariants.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@surfsense_backend/app/agents/chat/multi_agent_chat/shared/middleware/filesystem/backends/kb_postgres.py`:
- Around line 530-534: The query in the select statement is unnecessarily
fetching Chunk.content when canonical reads only require id, start_char, and
end_char for line-range mapping, causing wasteful DB I/O and memory overhead.
Remove Chunk.content from the select call at lines 530-534 to fetch only the
required fields (id, start_char, end_char, and position for ordering). Apply the
same fix to the similar queries at lines 544-547 and 558-563 that fetch
Chunk.content unnecessarily.
- Around line 523-524: The issue is that line 523 coerces None to an empty
string for source_markdown, and then the truthy check at line 543 treats empty
strings as falsy, incorrectly routing documents with intentionally empty
canonical markdown to the legacy XML fallback. To fix this, preserve the
distinction between None (no canonical body provided) and "" (intentionally
empty canonical body) by removing the coercion at line 523 that converts None to
"". Then update the conditional logic at lines 543-557 and 558-574 from truthy
checks to explicit None checks, so that documents with empty canonical markdown
are properly handled and not incorrectly routed to the legacy XML fallback.
In `@surfsense_backend/tests/integration/conftest.py`:
- Around line 163-167: The mock for chunk_markdown_with_spans is returning a
static chunk slice that doesn't depend on the actual input markdown, violating
the function contract. Modify the monkeypatched mock to accept the input
markdown as a parameter and dynamically generate ChunkSlice objects based on the
actual spans within that input, rather than returning a hardcoded fixed chunk
slice regardless of what markdown is passed to chunk_markdown_with_spans.
In `@surfsense_backend/tests/integration/document_upload/conftest.py`:
- Around line 291-295: The mock for chunk_markdown_with_spans in the monkeypatch
call returns a fixed ChunkSlice regardless of the markdown input, which breaks
span semantics and reduces test reliability. Modify the MagicMock to be a
function that accepts the markdown argument and derives the ChunkSlice from that
actual input instead of returning hardcoded values, ensuring the mock properly
processes different markdown inputs and maintains correct span semantics for the
integration test.
In `@surfsense_backend/tests/unit/utils/test_blocknote_to_markdown.py`:
- Around line 16-20: The test_block fixture and other shared mutable fixtures
defined at class level (including those at lines 101-104, 145-148, 170-177,
210-214, and 239-242) are being mutated across test methods, causing cross-test
state leakage and order-dependent behavior. Convert these class-level fixtures
to per-test fixtures by either using pytest fixture decorators with function
scope or by creating fresh local objects directly within each test method that
needs them. This ensures each test gets its own independent copy of the data
without mutations affecting other tests.
- Around line 154-157: The assert statements at lines 154-157, 165-168, 184-188,
and 202-206 are currently asserting generator objects (which are always truthy)
rather than the comparison results themselves, causing the tests to pass even
when blocknote_to_markdown() is wrong. Wrap each generator expression with the
all() function to properly validate that every comparison in the generator
evaluates to True, ensuring these assertions actually test the output from
blocknote_to_markdown() instead of just confirming a generator object exists.
In `@surfsense_web/lib/citations/citation-parser.ts`:
- Around line 102-109: In the code block where lineMatch is checked and a line
segment is pushed to the segments array, add validation to ensure the parsed
line-citation bounds follow the 1-based inclusive contract. Before pushing the
segment object in the segments.push() call, validate that startLine is greater
than or equal to 1 and that endLine is greater than or equal to startLine. Only
push the segment to the array if both validation conditions pass, otherwise skip
it to prevent invalid citations from propagating bad highlight ranges.
---
Nitpick comments:
In `@surfsense_backend/tests/integration/indexing_pipeline/test_index_spans.py`:
- Around line 32-37: The _assert_spans_address_body function needs to add
explicit validation for chunk ordering and contiguity to catch mispositioned
offsets. Add assertions that verify: chunks are ordered with monotonically
increasing start_char positions, each chunk is contiguous with the previous one
(each chunk's start_char equals the previous chunk's end_char), the first chunk
begins at position 0, and the final chunk ends at the length of the body. Use a
cursor variable that tracks the expected position of each successive chunk to
validate these invariants.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5a78f101-6f1e-4428-8d65-d0fb7cd5f164
📒 Files selected for processing (54)
surfsense_backend/alembic/versions/166_add_chunk_char_spans.pysurfsense_backend/app/agents/chat/multi_agent_chat/main_agent/middleware/kb_persistence/middleware.pysurfsense_backend/app/agents/chat/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.mdsurfsense_backend/app/agents/chat/multi_agent_chat/main_agent/tools/search_knowledge_base.pysurfsense_backend/app/agents/chat/multi_agent_chat/shared/middleware/filesystem/backends/kb_postgres.pysurfsense_backend/app/agents/chat/multi_agent_chat/shared/middleware/filesystem/backends/numbered_document.pysurfsense_backend/app/agents/chat/multi_agent_chat/shared/middleware/filesystem/tools/edit_file/index.pysurfsense_backend/app/agents/chat/multi_agent_chat/shared/middleware/filesystem/tools/move_file/helpers.pysurfsense_backend/app/agents/chat/multi_agent_chat/shared/middleware/filesystem/tools/read_file/index.pysurfsense_backend/app/agents/chat/multi_agent_chat/shared/middleware/filesystem/tools/rm/helpers.pysurfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.mdsurfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.mdsurfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_readonly_cloud.mdsurfsense_backend/app/config/__init__.pysurfsense_backend/app/db.pysurfsense_backend/app/indexing_pipeline/cache/cached_indexing.pysurfsense_backend/app/indexing_pipeline/chunk_reconciler.pysurfsense_backend/app/indexing_pipeline/document_chunker.pysurfsense_backend/app/indexing_pipeline/indexing_pipeline_service.pysurfsense_backend/app/retriever/chunks_hybrid_search.pysurfsense_backend/app/routes/documents_routes.pysurfsense_backend/app/routes/editor_routes.pysurfsense_backend/app/schemas/chunks.pysurfsense_backend/app/schemas/documents.pysurfsense_backend/app/utils/text_spans.pysurfsense_backend/tests/integration/agents/multi_agent_chat/test_kb_persistence_spans.pysurfsense_backend/tests/integration/conftest.pysurfsense_backend/tests/integration/document_upload/conftest.pysurfsense_backend/tests/integration/indexing_pipeline/adapters/test_file_upload_adapter.pysurfsense_backend/tests/integration/indexing_pipeline/test_index_editions.pysurfsense_backend/tests/integration/indexing_pipeline/test_index_spans.pysurfsense_backend/tests/integration/retriever/conftest.pysurfsense_backend/tests/integration/retriever/test_optimized_chunk_retriever.pysurfsense_backend/tests/integration/test_documents_by_chunk_route.pysurfsense_backend/tests/integration/test_editor_routes.pysurfsense_backend/tests/unit/agents/multi_agent_chat/tools/test_search_knowledge_base.pysurfsense_backend/tests/unit/indexing_pipeline/test_chunk_markdown_with_spans.pysurfsense_backend/tests/unit/indexing_pipeline/test_index_batch_parallel.pysurfsense_backend/tests/unit/middleware/test_b_filesystem_rm_rmdir_cloud.pysurfsense_backend/tests/unit/middleware/test_kb_persistence_filesystem_parity.pysurfsense_backend/tests/unit/middleware/test_numbered_document.pysurfsense_backend/tests/unit/utils/test_blocknote_to_markdown.pysurfsense_backend/tests/unit/utils/test_text_spans.pysurfsense_web/app/globals.csssurfsense_web/atoms/editor/editor-panel.atom.tssurfsense_web/components/assistant-ui/inline-citation.tsxsurfsense_web/components/citation-panel/citation-panel.tsxsurfsense_web/components/citations/citation-renderer.tsxsurfsense_web/components/editor-panel/editor-panel.tsxsurfsense_web/components/editor/plugins/citation-kit.tsxsurfsense_web/components/editor/source-code-editor.tsxsurfsense_web/components/layout/ui/right-panel/RightPanel.tsxsurfsense_web/contracts/types/document.types.tssurfsense_web/lib/citations/citation-parser.ts
| source_markdown = document.source_markdown or "" | ||
| document_type = ( |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not treat empty canonical markdown as “no canonical body.”
Line 523 coerces None to "", and Line 543 uses a truthy check. A document with intentionally empty canonical content is incorrectly routed to the legacy XML fallback, which breaks the canonical-read contract.
💡 Suggested fix
- source_markdown = document.source_markdown or ""
+ source_markdown = document.source_markdown
@@
- if source_markdown:
+ if source_markdown is not None:
ranges = compute_matched_line_ranges(
source_markdown,
[(r.id, r.start_char, r.end_char) for r in chunk_records],
matched,
)Also applies to: 543-557, 558-574
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@surfsense_backend/app/agents/chat/multi_agent_chat/shared/middleware/filesystem/backends/kb_postgres.py`
around lines 523 - 524, The issue is that line 523 coerces None to an empty
string for source_markdown, and then the truthy check at line 543 treats empty
strings as falsy, incorrectly routing documents with intentionally empty
canonical markdown to the legacy XML fallback. To fix this, preserve the
distinction between None (no canonical body provided) and "" (intentionally
empty canonical body) by removing the coercion at line 523 that converts None to
"". Then update the conditional logic at lines 543-557 and 558-574 from truthy
checks to explicit None checks, so that documents with empty canonical markdown
are properly handled and not incorrectly routed to the legacy XML fallback.
| chunk_rows = await session.execute( | ||
| select(Chunk.id, Chunk.content) | ||
| select(Chunk.id, Chunk.content, Chunk.start_char, Chunk.end_char) | ||
| .where(Chunk.document_id == document.id) | ||
| .order_by(Chunk.position, Chunk.id) | ||
| ) |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Avoid loading full chunk text on canonical reads.
Line 531 always fetches Chunk.content, but canonical reads only consume id/start_char/end_char for line-range mapping. This adds avoidable DB I/O and memory pressure on large documents.
💡 Suggested optimization
- chunk_rows = await session.execute(
- select(Chunk.id, Chunk.content, Chunk.start_char, Chunk.end_char)
- .where(Chunk.document_id == document.id)
- .order_by(Chunk.position, Chunk.id)
- )
+ if document.source_markdown is not None:
+ chunk_rows = await session.execute(
+ select(Chunk.id, Chunk.start_char, Chunk.end_char)
+ .where(Chunk.document_id == document.id)
+ .order_by(Chunk.position, Chunk.id)
+ )
+ else:
+ chunk_rows = await session.execute(
+ select(Chunk.id, Chunk.content, Chunk.start_char, Chunk.end_char)
+ .where(Chunk.document_id == document.id)
+ .order_by(Chunk.position, Chunk.id)
+ )Also applies to: 544-547, 558-563
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@surfsense_backend/app/agents/chat/multi_agent_chat/shared/middleware/filesystem/backends/kb_postgres.py`
around lines 530 - 534, The query in the select statement is unnecessarily
fetching Chunk.content when canonical reads only require id, start_char, and
end_char for line-range mapping, causing wasteful DB I/O and memory overhead.
Remove Chunk.content from the select call at lines 530-534 to fetch only the
required fields (id, start_char, end_char, and position for ordering). Apply the
same fix to the similar queries at lines 544-547 and 558-563 that fetch
Chunk.content unnecessarily.
| text = "Test chunk content." | ||
| mock = MagicMock(return_value=[ChunkSlice(text, 0, len(text))]) | ||
| monkeypatch.setattr( | ||
| "app.indexing_pipeline.cache.cached_indexing.chunk_text_hybrid", | ||
| "app.indexing_pipeline.cache.cached_indexing.chunk_markdown_with_spans", | ||
| mock, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Mocked chunk slices don’t reflect input markdown spans.
This fixture returns a fixed chunk/span pair regardless of input, which breaks the ChunkSlice contract and weakens span-sensitive integration assertions.
✅ Contract-preserving mock
- text = "Test chunk content."
- mock = MagicMock(return_value=[ChunkSlice(text, 0, len(text))])
+ def _mock_chunker(markdown: str, *args, **kwargs):
+ return [ChunkSlice(markdown, 0, len(markdown))]
+
+ mock = MagicMock(side_effect=_mock_chunker)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| text = "Test chunk content." | |
| mock = MagicMock(return_value=[ChunkSlice(text, 0, len(text))]) | |
| monkeypatch.setattr( | |
| "app.indexing_pipeline.cache.cached_indexing.chunk_text_hybrid", | |
| "app.indexing_pipeline.cache.cached_indexing.chunk_markdown_with_spans", | |
| mock, | |
| def _mock_chunker(markdown: str, *args, **kwargs): | |
| return [ChunkSlice(markdown, 0, len(markdown))] | |
| mock = MagicMock(side_effect=_mock_chunker) | |
| monkeypatch.setattr( | |
| "app.indexing_pipeline.cache.cached_indexing.chunk_markdown_with_spans", | |
| mock, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@surfsense_backend/tests/integration/conftest.py` around lines 163 - 167, The
mock for chunk_markdown_with_spans is returning a static chunk slice that
doesn't depend on the actual input markdown, violating the function contract.
Modify the monkeypatched mock to accept the input markdown as a parameter and
dynamically generate ChunkSlice objects based on the actual spans within that
input, rather than returning a hardcoded fixed chunk slice regardless of what
markdown is passed to chunk_markdown_with_spans.
| chunk = "Test chunk content." | ||
| monkeypatch.setattr( | ||
| "app.indexing_pipeline.cache.cached_indexing.chunk_text", | ||
| MagicMock(return_value=["Test chunk content."]), | ||
| "app.indexing_pipeline.cache.cached_indexing.chunk_markdown_with_spans", | ||
| MagicMock(return_value=[ChunkSlice(chunk, 0, len(chunk))]), | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Chunking mock should derive ChunkSlice from the incoming markdown.
Returning a fixed chunk/span pair for every call can invalidate span semantics and make integration checks less trustworthy.
✅ Contract-preserving mock
- chunk = "Test chunk content."
monkeypatch.setattr(
"app.indexing_pipeline.cache.cached_indexing.chunk_markdown_with_spans",
- MagicMock(return_value=[ChunkSlice(chunk, 0, len(chunk))]),
+ MagicMock(
+ side_effect=lambda markdown, *args, **kwargs: [
+ ChunkSlice(markdown, 0, len(markdown))
+ ]
+ ),
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| chunk = "Test chunk content." | |
| monkeypatch.setattr( | |
| "app.indexing_pipeline.cache.cached_indexing.chunk_text", | |
| MagicMock(return_value=["Test chunk content."]), | |
| "app.indexing_pipeline.cache.cached_indexing.chunk_markdown_with_spans", | |
| MagicMock(return_value=[ChunkSlice(chunk, 0, len(chunk))]), | |
| ) | |
| monkeypatch.setattr( | |
| "app.indexing_pipeline.cache.cached_indexing.chunk_markdown_with_spans", | |
| MagicMock( | |
| side_effect=lambda markdown, *args, **kwargs: [ | |
| ChunkSlice(markdown, 0, len(markdown)) | |
| ] | |
| ), | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@surfsense_backend/tests/integration/document_upload/conftest.py` around lines
291 - 295, The mock for chunk_markdown_with_spans in the monkeypatch call
returns a fixed ChunkSlice regardless of the markdown input, which breaks span
semantics and reduces test reliability. Modify the MagicMock to be a function
that accepts the markdown argument and derives the ChunkSlice from that actual
input instead of returning hardcoded values, ensuring the mock properly
processes different markdown inputs and maintains correct span semantics for the
integration test.
| const lineMatch = LINE_CITATION_REGEX.exec(captured); | ||
| if (lineMatch) { | ||
| segments.push({ | ||
| kind: "line", | ||
| documentId: Number.parseInt(lineMatch[1], 10), | ||
| startLine: Number.parseInt(lineMatch[2], 10), | ||
| endLine: Number.parseInt(lineMatch[3], 10), | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Validate line-citation bounds before emitting a "line" token.
At Line 102–109, parsed values are accepted without enforcing the 1-based inclusive contract (startLine >= 1 and endLine >= startLine). Invalid citations can propagate bad highlight ranges.
Suggested fix
const lineMatch = LINE_CITATION_REGEX.exec(captured);
if (lineMatch) {
+ const documentId = Number.parseInt(lineMatch[1], 10);
+ const startLine = Number.parseInt(lineMatch[2], 10);
+ const endLine = Number.parseInt(lineMatch[3], 10);
+ if (startLine < 1 || endLine < startLine) {
+ segments.push(match[0]);
+ lastIndex = match.index + match[0].length;
+ match = CITATION_REGEX.exec(text);
+ continue;
+ }
segments.push({
kind: "line",
- documentId: Number.parseInt(lineMatch[1], 10),
- startLine: Number.parseInt(lineMatch[2], 10),
- endLine: Number.parseInt(lineMatch[3], 10),
+ documentId,
+ startLine,
+ endLine,
});
} else if (captured.startsWith("http://") || captured.startsWith("https://")) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@surfsense_web/lib/citations/citation-parser.ts` around lines 102 - 109, In
the code block where lineMatch is checked and a line segment is pushed to the
segments array, add validation to ensure the parsed line-citation bounds follow
the 1-based inclusive contract. Before pushing the segment object in the
segments.push() call, validate that startLine is greater than or equal to 1 and
that endLine is greater than or equal to startLine. Only push the segment to the
array if both validation conditions pass, otherwise skip it to prevent invalid
citations from propagating bad highlight ranges.
21dfdd5 to
5625ca1
Compare
|
@Glodykajabika can you please target the dev branch ? |
Description
Adds comprehensive unit tests for the
blocknote_to_markdown()function as specified in issue #1519. Tests cover all block types, inline styles, edge cases, and nested content with proper indentation.The new test file
tests/unit/utils/test_blocknote_to_markdown.pyincludes test cases for:props.start, checklists checked/unchecked)Motivation and Context
The
blocknote_to_markdown()function is a critical utility for converting BlockNote editor JSON to Markdown, but it had no unit tests. This PR provides comprehensive test coverage to ensure the function works correctly across all supported block types and edge cases.closes#1519
Screenshots
N/A (unit tests, no visual changes)
API Changes
Change Type
Testing Performed
All tests pass locally with:
Checklist
Summary by CodeRabbit
New Features
[citation:d<id>#L<start>-<end>]) and clickable inline tokens.Improvements