mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
ec2fa07350
Signed-off-by: Joe P <joe@basicmemory.com> Co-authored-by: Claude <noreply@anthropic.com>
212 lines
7.2 KiB
Python
212 lines
7.2 KiB
Python
"""Tests for markdown plugins."""
|
|
|
|
from textwrap import dedent
|
|
from markdown_it import MarkdownIt
|
|
from markdown_it.token import Token
|
|
|
|
from basic_memory.markdown.plugins import (
|
|
observation_plugin,
|
|
relation_plugin,
|
|
is_observation,
|
|
is_explicit_relation,
|
|
parse_relation,
|
|
parse_inline_relations,
|
|
)
|
|
|
|
|
|
def test_observation_plugin():
|
|
"""Test observation plugin."""
|
|
# Set up markdown-it instance
|
|
md = MarkdownIt().use(observation_plugin)
|
|
|
|
# Test basic observation with all features
|
|
content = dedent("""
|
|
- [design] Basic observation #tag1 #tag2 (with context)
|
|
""")
|
|
|
|
tokens = md.parse(content)
|
|
token = [t for t in tokens if t.type == "inline"][0]
|
|
assert "observation" in token.meta
|
|
obs = token.meta["observation"]
|
|
assert obs["category"] == "design"
|
|
assert obs["content"] == "Basic observation #tag1 #tag2"
|
|
assert set(obs["tags"]) == {"tag1", "tag2"}
|
|
assert obs["context"] == "with context"
|
|
|
|
# Test without category
|
|
content = "- Basic observation #tag1 (context)"
|
|
token = [t for t in md.parse(content) if t.type == "inline"][0]
|
|
obs = token.meta["observation"]
|
|
assert obs["category"] is None
|
|
assert obs["content"] == "Basic observation #tag1"
|
|
assert obs["tags"] == ["tag1"]
|
|
assert obs["context"] == "context"
|
|
|
|
# Test without tags
|
|
content = "- [note] Basic observation (context)"
|
|
token = [t for t in md.parse(content) if t.type == "inline"][0]
|
|
obs = token.meta["observation"]
|
|
assert obs["category"] == "note"
|
|
assert obs["content"] == "Basic observation"
|
|
assert obs["tags"] is None
|
|
assert obs["context"] == "context"
|
|
|
|
|
|
def test_observation_edge_cases():
|
|
"""Test observation parsing edge cases."""
|
|
# Test non-inline token
|
|
token = Token("paragraph", "", 0)
|
|
assert not is_observation(token)
|
|
|
|
# Test empty content
|
|
token = Token("inline", "", 0)
|
|
assert not is_observation(token)
|
|
|
|
# Test markdown task
|
|
token = Token("inline", "[ ] Task item", 0)
|
|
assert not is_observation(token)
|
|
|
|
# Test completed task
|
|
token = Token("inline", "[x] Done task", 0)
|
|
assert not is_observation(token)
|
|
|
|
# Test in-progress task
|
|
token = Token("inline", "[-] Ongoing task", 0)
|
|
assert not is_observation(token)
|
|
|
|
|
|
def test_observation_excludes_markdown_and_wiki_links():
|
|
"""Test that markdown links and wiki links are NOT parsed as observations.
|
|
|
|
This test validates the fix for issue #247 where:
|
|
- [text](url) markdown links were incorrectly parsed as observations
|
|
- [[text]] wiki links were incorrectly parsed as observations
|
|
"""
|
|
# Test markdown links are NOT observations
|
|
token = Token("inline", "[Click here](https://example.com)", 0)
|
|
assert not is_observation(token), "Markdown links should not be parsed as observations"
|
|
|
|
token = Token("inline", "[Documentation](./docs/readme.md)", 0)
|
|
assert not is_observation(token), "Relative markdown links should not be parsed as observations"
|
|
|
|
token = Token("inline", "[Empty link]()", 0)
|
|
assert not is_observation(token), "Empty markdown links should not be parsed as observations"
|
|
|
|
# Test wiki links are NOT observations
|
|
token = Token("inline", "[[SomeWikiPage]]", 0)
|
|
assert not is_observation(token), "Wiki links should not be parsed as observations"
|
|
|
|
token = Token("inline", "[[Multi Word Page]]", 0)
|
|
assert not is_observation(token), "Multi-word wiki links should not be parsed as observations"
|
|
|
|
# Test nested brackets are NOT observations
|
|
token = Token("inline", "[[Nested [[Inner]] Link]]", 0)
|
|
assert not is_observation(token), "Nested wiki links should not be parsed as observations"
|
|
|
|
# Test valid observations still work (should return True)
|
|
token = Token("inline", "[category] This is a valid observation", 0)
|
|
assert is_observation(token), "Valid observations should still be parsed correctly"
|
|
|
|
token = Token("inline", "[design] Valid observation #tag", 0)
|
|
assert is_observation(token), "Valid observations with tags should still work"
|
|
|
|
token = Token("inline", "Just some text #tag", 0)
|
|
assert is_observation(token), "Tag-only observations should still work"
|
|
|
|
# Test edge cases that should NOT be observations
|
|
token = Token("inline", "[]Empty brackets", 0)
|
|
assert not is_observation(token), "Empty category brackets should not be observations"
|
|
|
|
token = Token("inline", "[category]No space after category", 0)
|
|
assert not is_observation(token), "No space after category should not be valid observation"
|
|
|
|
|
|
def test_relation_plugin():
|
|
"""Test relation plugin."""
|
|
md = MarkdownIt().use(relation_plugin)
|
|
|
|
# Test explicit relation with all features
|
|
content = dedent("""
|
|
- implements [[Component]] (with context)
|
|
""")
|
|
|
|
tokens = md.parse(content)
|
|
token = [t for t in tokens if t.type == "inline"][0]
|
|
assert "relations" in token.meta
|
|
rel = token.meta["relations"][0]
|
|
assert rel["type"] == "implements"
|
|
assert rel["target"] == "Component"
|
|
assert rel["context"] == "with context"
|
|
|
|
# Test implicit relations in text
|
|
content = "Some text with a [[Link]] and [[Another Link]]"
|
|
token = [t for t in md.parse(content) if t.type == "inline"][0]
|
|
rels = token.meta["relations"]
|
|
assert len(rels) == 2
|
|
assert rels[0]["type"] == "links to"
|
|
assert rels[0]["target"] == "Link"
|
|
assert rels[1]["target"] == "Another Link"
|
|
|
|
|
|
def test_relation_edge_cases():
|
|
"""Test relation parsing edge cases."""
|
|
# Test non-inline token
|
|
token = Token("paragraph", "", 0)
|
|
assert not is_explicit_relation(token)
|
|
|
|
# Test empty content
|
|
token = Token("inline", "", 0)
|
|
assert not is_explicit_relation(token)
|
|
|
|
# Test incomplete relation (missing target)
|
|
token = Token("inline", "relates_to [[]]", 0)
|
|
result = parse_relation(token)
|
|
assert result is None
|
|
|
|
# Test non-relation content
|
|
token = Token("inline", "Just some text", 0)
|
|
result = parse_relation(token)
|
|
assert result is None
|
|
|
|
# Test invalid inline link (empty target)
|
|
assert not parse_inline_relations("Text with [[]] empty link")
|
|
|
|
# Test nested links (avoid duplicates)
|
|
result = parse_inline_relations("Text with [[Outer [[Inner]] Link]]")
|
|
assert len(result) == 1
|
|
assert result[0]["target"] == "Outer [[Inner]] Link"
|
|
|
|
|
|
def test_combined_plugins():
|
|
"""Test both plugins working together."""
|
|
md = MarkdownIt().use(observation_plugin).use(relation_plugin)
|
|
|
|
content = dedent("""
|
|
# Section
|
|
- [design] Observation with [[Link]] #tag (context)
|
|
- implements [[Component]] (details)
|
|
- Just a [[Reference]] in text
|
|
|
|
Some text with a [[Link]] reference.
|
|
""")
|
|
|
|
tokens = md.parse(content)
|
|
inline_tokens = [t for t in tokens if t.type == "inline"]
|
|
|
|
# First token has both observation and relation
|
|
obs_token = inline_tokens[1]
|
|
assert "observation" in obs_token.meta
|
|
assert "relations" in obs_token.meta
|
|
|
|
# Second token has explicit relation
|
|
rel_token = inline_tokens[2]
|
|
assert "relations" in rel_token.meta
|
|
rel = rel_token.meta["relations"][0]
|
|
assert rel["type"] == "implements"
|
|
|
|
# Third token has implicit relation
|
|
text_token = inline_tokens[4]
|
|
assert "relations" in text_token.meta
|
|
link = text_token.meta["relations"][0]
|
|
assert link["type"] == "links to"
|