mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-05-18 05:05:39 +02:00
0.37.0 - **Director Planning** — Multi-step todo lists in director chat plus a Generate long progress action for multi-beat scene arcs. - **Auto Narration** — Unified auto-narration replacing the old Narrate after Dialogue toggle, with a chance slider and weighted action mix. - **LLM Prompt Templates Manager** — Dedicated UI tab for viewing, creating, editing, and deleting prompt templates. - **Character Folders** — Collapsible folders in the World Editor character list, synced across linked scenes. - **OpenAI Compatible TTS** — Connect any number of OpenAI-compatible TTS servers in parallel. - **KoboldCpp TTS Auto-Setup** — KoboldCpp clients with a TTS model loaded register themselves as a TTS backend. - **Model Testing Harness** — Bundled scene that runs basic capability tests against any connected LLM. Plus 27 improvements and 28 bug fixes
150 lines
6.2 KiB
Python
150 lines
6.2 KiB
Python
"""Unit tests for talemate.files (scene directory listing)."""
|
|
|
|
import os
|
|
import pytest
|
|
|
|
from talemate.files import list_scenes_directory, _list_files_and_directories
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fixtures: build a fake scenes/ tree we can assert against deterministically
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.fixture
|
|
def scene_tree(tmp_path):
|
|
"""Construct a 'scenes/' directory mirroring the real talemate layout.
|
|
|
|
The real layout has scene JSONs at scenes/<scene>/*.json and shared
|
|
character images at scenes/characters/*.png|webp. The helper's fnmatch
|
|
patterns ('characters/*.png' and '*/*.json') only match these top-level
|
|
locations relative to the scenes root.
|
|
"""
|
|
scenes_root = tmp_path / "scenes"
|
|
scenes_root.mkdir(parents=True)
|
|
|
|
files = {
|
|
# Scene JSONs (one folder per scene)
|
|
"alice_in_wonderland/alice.json": '{"a": 1}',
|
|
"bob_adventure/bob.json": '{"b": 1}',
|
|
# Top-level character images
|
|
"characters/alice.png": b"\x89PNG",
|
|
"characters/queen.webp": b"WEBP",
|
|
# Files that must be filtered out:
|
|
# - non-image extension in characters/
|
|
"characters/notes.txt": "ignored extension",
|
|
# - JSON inside a 'nodes' directory must be skipped
|
|
"alice_in_wonderland/nodes/scene-loop.json": '{"node": true}',
|
|
# - changelog directory must be skipped
|
|
"alice_in_wonderland/changelog/0001.json": '{"x": true}',
|
|
# - assets directory must be skipped
|
|
"assets/portrait.png": b"\x89PNG",
|
|
"assets/library.json": '{"asset": true}',
|
|
}
|
|
|
|
paths = {}
|
|
for rel, content in files.items():
|
|
full = scenes_root / rel
|
|
full.parent.mkdir(parents=True, exist_ok=True)
|
|
mode = "wb" if isinstance(content, bytes) else "w"
|
|
with open(full, mode) as fh:
|
|
fh.write(content)
|
|
paths[rel] = str(full)
|
|
|
|
return {"root": str(scenes_root), "tmp": str(tmp_path), "paths": paths}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _list_files_and_directories: the underlying helper
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestListFilesAndDirectories:
|
|
def test_lists_images_and_jsons_when_images_enabled(self, scene_tree):
|
|
result = _list_files_and_directories(scene_tree["root"], ".", list_images=True)
|
|
|
|
# Convert to set for ordering-independent comparison
|
|
result_set = set(result)
|
|
expected = {
|
|
scene_tree["paths"]["characters/alice.png"],
|
|
scene_tree["paths"]["characters/queen.webp"],
|
|
scene_tree["paths"]["alice_in_wonderland/alice.json"],
|
|
scene_tree["paths"]["bob_adventure/bob.json"],
|
|
}
|
|
assert result_set == expected
|
|
|
|
def test_omits_images_when_list_images_false(self, scene_tree):
|
|
result = _list_files_and_directories(scene_tree["root"], ".", list_images=False)
|
|
# Only json files (not in nodes/changelog/assets) should appear
|
|
for path in result:
|
|
assert path.endswith(".json")
|
|
result_set = set(result)
|
|
assert result_set == {
|
|
scene_tree["paths"]["alice_in_wonderland/alice.json"],
|
|
scene_tree["paths"]["bob_adventure/bob.json"],
|
|
}
|
|
|
|
def test_skips_json_inside_nodes_directory(self, scene_tree):
|
|
result = _list_files_and_directories(scene_tree["root"], ".", list_images=True)
|
|
# nodes/scene-loop.json must NOT appear
|
|
for path in result:
|
|
assert os.sep + "nodes" + os.sep not in path
|
|
|
|
def test_skips_changelog_directory(self, scene_tree):
|
|
result = _list_files_and_directories(scene_tree["root"], ".", list_images=True)
|
|
for path in result:
|
|
assert os.sep + "changelog" + os.sep not in path
|
|
|
|
def test_skips_assets_directory(self, scene_tree):
|
|
result = _list_files_and_directories(scene_tree["root"], ".", list_images=True)
|
|
for path in result:
|
|
assert os.sep + "assets" + os.sep not in path
|
|
|
|
def test_ignores_unsupported_extensions(self, scene_tree):
|
|
result = _list_files_and_directories(scene_tree["root"], ".", list_images=True)
|
|
# The 'characters/notes.txt' file should be filtered out
|
|
for path in result:
|
|
assert not path.endswith(".txt")
|
|
|
|
def test_returns_empty_list_for_empty_root(self, tmp_path):
|
|
empty_dir = tmp_path / "empty_scenes"
|
|
empty_dir.mkdir()
|
|
result = _list_files_and_directories(str(empty_dir), ".", list_images=True)
|
|
assert result == []
|
|
|
|
def test_returns_empty_for_nonexistent_root(self, tmp_path):
|
|
# os.walk silently returns nothing for missing roots; the helper
|
|
# must follow that behaviour rather than raising.
|
|
result = _list_files_and_directories(
|
|
str(tmp_path / "does_not_exist"), ".", list_images=True
|
|
)
|
|
assert result == []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# list_scenes_directory: thin wrapper around the helper
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestListScenesDirectory:
|
|
def test_walks_scenes_subdir_of_cwd(self, scene_tree, monkeypatch):
|
|
# list_scenes_directory uses os.getcwd() / "scenes" as the root
|
|
monkeypatch.chdir(scene_tree["tmp"])
|
|
result = list_scenes_directory(list_images=True)
|
|
|
|
# The fake scenes tree has 4 matching files (2 JSONs + 2 images)
|
|
assert len(result) == 4
|
|
result_set = set(result)
|
|
assert scene_tree["paths"]["characters/alice.png"] in result_set
|
|
assert scene_tree["paths"]["alice_in_wonderland/alice.json"] in result_set
|
|
|
|
def test_passes_list_images_flag_through(self, scene_tree, monkeypatch):
|
|
monkeypatch.chdir(scene_tree["tmp"])
|
|
json_only = list_scenes_directory(list_images=False)
|
|
assert all(path.endswith(".json") for path in json_only)
|
|
|
|
def test_returns_empty_when_no_scenes_dir(self, tmp_path, monkeypatch):
|
|
monkeypatch.chdir(str(tmp_path))
|
|
# No scenes/ directory exists -> empty list (os.walk on missing path)
|
|
assert list_scenes_directory(list_images=True) == []
|