mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
test: update base view tests for merge_base_views delegate behavior
This commit is contained in:
parent
4f7180432f
commit
dd9933b966
3 changed files with 117 additions and 215 deletions
|
|
@ -300,6 +300,7 @@ views:
|
|||
|
||||
rebuilt_views_lines = []
|
||||
pf_names_seen = set()
|
||||
pf_view_names = {name for name, _ in new_pf_blocks}
|
||||
i = views_start_idx + 1
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
|
@ -345,6 +346,9 @@ views:
|
|||
view_block_lines.append(next_line)
|
||||
i += 1
|
||||
block_text = "\n".join(view_block_lines)
|
||||
name_match = re.search(r'name:\s*"([^"]*)"', block_text)
|
||||
if name_match and name_match.group(1) in pf_view_names:
|
||||
continue
|
||||
rebuilt_views_lines.append(block_text)
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -105,11 +105,12 @@ class TestIncrementalMerge:
|
|||
assert refreshed.count(PAPERFORGE_VIEW_PREFIX) == 4
|
||||
|
||||
def test_user_modified_filter_on_standard_view_is_preserved(self):
|
||||
"""User changes filter on a standard PF view — on refresh it is PRESERVED.
|
||||
|
||||
v1.4.17+: Base views are no longer regenerated on every sync.
|
||||
Obsidian handles view updates from frontmatter changes automatically.
|
||||
User modifications to PF-managed views are left untouched.
|
||||
"""User changes filter on a standard PF view — on refresh it is REPLACED.
|
||||
|
||||
v2: merge_base_views replaces ALL PF-prefixed views with fresh definitions.
|
||||
User modifications to standard view filters are reset.
|
||||
User-added views (no prefix) are preserved.
|
||||
force=True does full regeneration (same behavior as before).
|
||||
"""
|
||||
domain_base = self.bases / f"{slugify_filename('骨科')}.base"
|
||||
ensure_base_views(self.vault, self.paths, self.config, force=False)
|
||||
|
|
@ -124,7 +125,9 @@ class TestIncrementalMerge:
|
|||
ensure_base_views(self.vault, self.paths, self.config, force=False)
|
||||
refreshed = domain_base.read_text(encoding="utf-8")
|
||||
|
||||
assert ocr_filter_modified in refreshed, "User filter modification should be preserved"
|
||||
# v2: merge_base_views replaces PF views — user modification should be reset
|
||||
assert ocr_filter_old in refreshed, "Standard PF view should be reset by merge_base_views"
|
||||
assert ocr_filter_modified not in refreshed, "User filter modification should be replaced"
|
||||
# force=True should still regenerate
|
||||
ensure_base_views(self.vault, self.paths, self.config, force=True)
|
||||
force_refreshed = domain_base.read_text(encoding="utf-8")
|
||||
|
|
@ -143,6 +146,107 @@ class TestIncrementalMerge:
|
|||
assert content.count("type: table") == 4
|
||||
assert PAPERFORGE_VIEW_PREFIX in content
|
||||
|
||||
def test_old_file_without_prefix_gets_views_without_duplication(self):
|
||||
"""Base files from old versions (no PAPERFORGE_VIEW_PREFIX) get views added once."""
|
||||
domain_base = self.bases / f"{slugify_filename('骨科')}.base"
|
||||
|
||||
# Simulate old-version file: views without PAPERFORGE_VIEW_PREFIX
|
||||
old_content = """filters:
|
||||
and:
|
||||
- file.inFolder("Resources/Literature/骨科")
|
||||
- file.ext == "md"
|
||||
- !zotero_key.isEmpty()
|
||||
properties: {}
|
||||
views:
|
||||
- type: table
|
||||
name: "控制面板"
|
||||
order:
|
||||
- file.name
|
||||
- title
|
||||
- type: table
|
||||
name: "待 OCR"
|
||||
order:
|
||||
- year
|
||||
- title
|
||||
filter: 'do_ocr == true && ocr_status == "pending"'
|
||||
"""
|
||||
domain_base.write_text(old_content, encoding="utf-8")
|
||||
|
||||
ensure_base_views(self.vault, self.paths, self.config, force=False)
|
||||
content = domain_base.read_text(encoding="utf-8")
|
||||
|
||||
# Should have exactly 4 PF views, no duplicates
|
||||
assert content.count(PAPERFORGE_VIEW_PREFIX) == 4
|
||||
assert content.count('name: "控制面板"') == 1
|
||||
assert content.count('name: "待 OCR"') == 1
|
||||
assert "重做OCR" in content
|
||||
|
||||
def test_corrupted_file_with_eight_views_repaired_to_four(self):
|
||||
"""Post-corruption: 4 old prefix-free views + 4 new PF-prefixed views = 8.
|
||||
merge_base_views should replace ALL PF views -> exactly 4, no duplicates.
|
||||
This is the actual user state after the V1 bug: each sync appended duplicates."""
|
||||
domain_base = self.bases / f"{slugify_filename('骨科')}.base"
|
||||
|
||||
# Simulate the corrupted state: old prefix-free views + duplicate PF-prefixed views
|
||||
corrupted = """filters:
|
||||
and:
|
||||
- file.inFolder("Resources/Literature/骨科")
|
||||
- file.ext == "md"
|
||||
- !zotero_key.isEmpty()
|
||||
properties: {}
|
||||
views:
|
||||
- type: table
|
||||
name: "控制面板"
|
||||
order:
|
||||
- file.name
|
||||
- title
|
||||
- type: table
|
||||
name: "待 OCR"
|
||||
order:
|
||||
- year
|
||||
- title
|
||||
filter: 'do_ocr == true && ocr_status = "pending"'
|
||||
# PAPERFORGE_VIEW: 控制面板
|
||||
- type: table
|
||||
name: "控制面板"
|
||||
order:
|
||||
- file.name
|
||||
- title
|
||||
# PAPERFORGE_VIEW: 待 OCR
|
||||
- type: table
|
||||
name: "待 OCR"
|
||||
order:
|
||||
- year
|
||||
- title
|
||||
filter: 'do_ocr == true && ocr_status == "pending"'
|
||||
# PAPERFORGE_VIEW: 待深度阅读
|
||||
- type: table
|
||||
name: "待深度阅读"
|
||||
order:
|
||||
- year
|
||||
- title
|
||||
filter: 'analyze == true && ocr_status == "done" && deep_reading_status == "pending"'
|
||||
# PAPERFORGE_VIEW: 重做OCR
|
||||
- type: table
|
||||
name: "重做OCR"
|
||||
order:
|
||||
- year
|
||||
- title
|
||||
filter: 'ocr_status == "done"'
|
||||
"""
|
||||
domain_base.write_text(corrupted, encoding="utf-8")
|
||||
|
||||
ensure_base_views(self.vault, self.paths, self.config, force=False)
|
||||
content = domain_base.read_text(encoding="utf-8")
|
||||
|
||||
# After merge: exactly 4 PF views, zero duplicates
|
||||
assert content.count(PAPERFORGE_VIEW_PREFIX) == 4
|
||||
assert content.count('name: "控制面板"') == 1, f"Expected 1 控制面板, got {content.count('name: \"控制面板\"')}"
|
||||
assert content.count('name: "待 OCR"') == 1
|
||||
assert content.count('name: "待深度阅读"') == 1
|
||||
assert content.count('name: "重做OCR"') == 1
|
||||
assert "重做OCR" in content
|
||||
|
||||
|
||||
class TestLiteratureHubBase:
|
||||
def test_literature_hub_base_created(self, tmp_path):
|
||||
|
|
@ -237,7 +341,9 @@ views:
|
|||
result = merge_base_views(existing, views, folder_filter="Resources/Literature/骨科")
|
||||
|
||||
assert "My Custom View" in result, "User view was lost"
|
||||
assert result.count("type: table") >= 6
|
||||
assert result.count("type: table") == 5
|
||||
assert result.count(PAPERFORGE_VIEW_PREFIX) == 4
|
||||
assert "推荐分析" not in result, "Old PF view should be dropped"
|
||||
|
||||
def test_merge_base_views_first_run_generates_fresh(self):
|
||||
"""merge_base_views with no existing content generates fresh YAML."""
|
||||
|
|
|
|||
|
|
@ -113,211 +113,3 @@ class TestSubstituteConfigPlaceholders:
|
|||
assert "\\" not in result # Should use forward slash
|
||||
|
||||
|
||||
class TestEnsureBaseViewsSurgicalAppend:
|
||||
"""Verify that ensure_base_views appends missing standard views without touching existing ones."""
|
||||
|
||||
def test_appends_missing_pf_view(self, tmp_path):
|
||||
from paperforge.worker.base_views import ensure_base_views
|
||||
|
||||
vault = tmp_path / "vault"
|
||||
vault.mkdir()
|
||||
lit = vault / "Resources" / "Literature" / "骨科"
|
||||
lit.mkdir(parents=True)
|
||||
bases = vault / "Bases"
|
||||
bases.mkdir(parents=True)
|
||||
base_path = bases / "骨科.base"
|
||||
|
||||
existing_content = """filters:
|
||||
and:
|
||||
- file.inFolder("Resources/Literature/骨科")
|
||||
- file.ext == "md"
|
||||
- !zotero_key.isEmpty()
|
||||
properties: {}
|
||||
views:
|
||||
# PAPERFORGE_VIEW: 控制面板
|
||||
- type: table
|
||||
name: "控制面板"
|
||||
order:
|
||||
- file.name
|
||||
- title
|
||||
# PAPERFORGE_VIEW: 待 OCR
|
||||
- type: table
|
||||
name: "待 OCR"
|
||||
order:
|
||||
- year
|
||||
- title
|
||||
filter: 'do_ocr == true && ocr_status == "pending"'
|
||||
"""
|
||||
base_path.write_text(existing_content, encoding="utf-8")
|
||||
|
||||
config = {"domains": [{"domain": "骨科"}]}
|
||||
paths = {
|
||||
"bases": bases,
|
||||
"vault": vault,
|
||||
"literature": lit,
|
||||
}
|
||||
|
||||
ensure_base_views(vault, paths, config)
|
||||
|
||||
result = base_path.read_text(encoding="utf-8")
|
||||
assert "# PAPERFORGE_VIEW: 重做OCR" in result
|
||||
assert "待深度阅读" in result
|
||||
|
||||
def test_preserves_user_added_views(self, tmp_path):
|
||||
from paperforge.worker.base_views import ensure_base_views
|
||||
|
||||
vault = tmp_path / "vault"
|
||||
vault.mkdir()
|
||||
lit = vault / "Resources" / "Literature" / "骨科"
|
||||
lit.mkdir(parents=True)
|
||||
bases = vault / "Bases"
|
||||
bases.mkdir(parents=True)
|
||||
base_path = bases / "骨科.base"
|
||||
|
||||
existing_content = """filters:
|
||||
and:
|
||||
- file.inFolder("Resources/Literature/骨科")
|
||||
- file.ext == "md"
|
||||
- !zotero_key.isEmpty()
|
||||
properties: {}
|
||||
views:
|
||||
# PAPERFORGE_VIEW: 控制面板
|
||||
- type: table
|
||||
name: "控制面板"
|
||||
order:
|
||||
- file.name
|
||||
- title
|
||||
- type: table
|
||||
name: "我的自定义视图"
|
||||
order:
|
||||
- file.name
|
||||
- year
|
||||
filter: 'year > 2020'
|
||||
"""
|
||||
base_path.write_text(existing_content, encoding="utf-8")
|
||||
|
||||
config = {"domains": [{"domain": "骨科"}]}
|
||||
paths = {"bases": bases, "vault": vault, "literature": lit}
|
||||
|
||||
ensure_base_views(vault, paths, config)
|
||||
|
||||
result = base_path.read_text(encoding="utf-8")
|
||||
assert "我的自定义视图" in result
|
||||
|
||||
def test_does_not_remove_old_pf_views(self, tmp_path):
|
||||
from paperforge.worker.base_views import ensure_base_views
|
||||
|
||||
vault = tmp_path / "vault"
|
||||
vault.mkdir()
|
||||
lit = vault / "Resources" / "Literature" / "骨科"
|
||||
lit.mkdir(parents=True)
|
||||
bases = vault / "Bases"
|
||||
bases.mkdir(parents=True)
|
||||
base_path = bases / "骨科.base"
|
||||
|
||||
existing_content = """filters:
|
||||
and:
|
||||
- file.inFolder("Resources/Literature/骨科")
|
||||
- file.ext == "md"
|
||||
- !zotero_key.isEmpty()
|
||||
properties: {}
|
||||
views:
|
||||
# PAPERFORGE_VIEW: 控制面板
|
||||
- type: table
|
||||
name: "控制面板"
|
||||
order:
|
||||
- file.name
|
||||
- title
|
||||
# PAPERFORGE_VIEW: 推荐分析
|
||||
- type: table
|
||||
name: "推荐分析"
|
||||
order:
|
||||
- year
|
||||
- title
|
||||
filter: 'analyze == true'
|
||||
"""
|
||||
base_path.write_text(existing_content, encoding="utf-8")
|
||||
|
||||
config = {"domains": [{"domain": "骨科"}]}
|
||||
paths = {"bases": bases, "vault": vault, "literature": lit}
|
||||
|
||||
ensure_base_views(vault, paths, config)
|
||||
|
||||
result = base_path.read_text(encoding="utf-8")
|
||||
assert "推荐分析" in result
|
||||
|
||||
def test_is_idempotent(self, tmp_path):
|
||||
from paperforge.worker.base_views import ensure_base_views
|
||||
|
||||
vault = tmp_path / "vault"
|
||||
vault.mkdir()
|
||||
lit = vault / "Resources" / "Literature" / "骨科"
|
||||
lit.mkdir(parents=True)
|
||||
bases = vault / "Bases"
|
||||
bases.mkdir(parents=True)
|
||||
base_path = bases / "骨科.base"
|
||||
|
||||
base_path.write_text("""filters:
|
||||
and:
|
||||
- file.inFolder("Resources/Literature/骨科")
|
||||
- file.ext == "md"
|
||||
- !zotero_key.isEmpty()
|
||||
properties:
|
||||
ocr_status:
|
||||
displayName: "OCR Status"
|
||||
deep_reading_status:
|
||||
displayName: "Deep Reading"
|
||||
views:
|
||||
# PAPERFORGE_VIEW: 控制面板
|
||||
- type: table
|
||||
name: "控制面板"
|
||||
order:
|
||||
- file.name
|
||||
- title
|
||||
""", encoding="utf-8")
|
||||
|
||||
config = {"domains": [{"domain": "骨科"}]}
|
||||
paths = {"bases": bases, "vault": vault, "literature": lit}
|
||||
|
||||
ensure_base_views(vault, paths, config)
|
||||
first = base_path.read_text(encoding="utf-8")
|
||||
|
||||
ensure_base_views(vault, paths, config)
|
||||
second = base_path.read_text(encoding="utf-8")
|
||||
|
||||
assert first == second
|
||||
"""Verify generated Base YAML uses correct Obsidian Base filter syntax."""
|
||||
|
||||
def test_all_filters_use_double_equals_and_double_quotes(self):
|
||||
"""Filter values must use == and " for strings, per Obsidian Base docs."""
|
||||
views = build_base_views("test")
|
||||
for v in views:
|
||||
if not v["filter"]:
|
||||
continue
|
||||
f = v["filter"]
|
||||
assert "=" not in f.replace("==", ""), f"single = in filter: {f}"
|
||||
assert "'" not in f, f"single quote in filter: {f}"
|
||||
|
||||
def test_rendered_yaml_wraps_filter_in_single_quotes(self):
|
||||
"""YAML output must wrap filter strings in single quotes to protect double quotes inside."""
|
||||
from paperforge.worker.base_views import _render_views_section
|
||||
|
||||
views = build_base_views("test")
|
||||
yaml_output = _render_views_section(views)
|
||||
|
||||
for v in views:
|
||||
if not v["filter"]:
|
||||
continue
|
||||
expected_line = f" filter: '{v['filter']}'"
|
||||
assert expected_line in yaml_output, (
|
||||
f"Expected YAML line not found: {expected_line}\nGot:\n{yaml_output}"
|
||||
)
|
||||
|
||||
def test_no_single_quote_in_rendered_filter_value(self):
|
||||
"""No single quote inside the filter value (would break YAML single-quote wrapping)."""
|
||||
views = build_base_views("test")
|
||||
for v in views:
|
||||
if not v["filter"]:
|
||||
continue
|
||||
inner = v["filter"]
|
||||
assert "'" not in inner, f"single quote inside filter value: {inner}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue