From eca44a15bde17548fd44ef6e451880ad321ac075 Mon Sep 17 00:00:00 2001 From: DazedAnon Date: Thu, 9 Jul 2026 07:29:20 -0500 Subject: [PATCH] Trim tests --- tests/test_wolf_codes.py | 108 +++------- tests/test_wolf_names.py | 26 +-- tests/test_wolf_wrap_search.py | 351 ++++++++------------------------ tests/test_wolfdawn_relayout.py | 125 ++---------- util/wolfdawn/codes.py | 21 -- util/wolfdawn/wrap_search.py | 135 +----------- 6 files changed, 129 insertions(+), 637 deletions(-) diff --git a/tests/test_wolf_codes.py b/tests/test_wolf_codes.py index dd44b4e..3766c78 100644 --- a/tests/test_wolf_codes.py +++ b/tests/test_wolf_codes.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Tests for WOLF inline control-code repair.""" +"""Tests for WOLF inline control-code repair and font scaling.""" from __future__ import annotations @@ -59,95 +59,33 @@ class WolfCodesRepairTests(unittest.TestCase): "Fortune-teller\nHa!\\^", ) - def test_apply_font_size_replaces_existing_codes(self): - text = r"Go to the \c[21]\f[20]tavern\c[19]\f[18]!" - new_text, count = wolf_codes.apply_font_size(text, 16) - self.assertEqual(count, 3) # 2 replaced + leading body - self.assertTrue(new_text.startswith(r"\f[16]")) - self.assertIn(r"\f[16]", new_text) + +class WolfFontScaleTests(unittest.TestCase): + def test_scale_keeps_emphasis_ratio_and_leads_with_body(self): + # Cafe-style mid-line emphasis: body must lead so Wolf sets line height. + text = ( + '"That \\c[21]\\f[20]cafe\\c[19]\\f[18] over there has been\n' + "packed lately.\"" + ) + new_text, _ = wolf_codes.scale_font_sizes(text, 13) + self.assertTrue(new_text.startswith(r"\f[13]")) + # 20 * 13/18 ≈ 14.4 → 14; body 13 + self.assertIn(r"\c[21]\f[14]cafe\c[19]\f[13]", new_text) self.assertNotIn(r"\f[20]", new_text) self.assertNotIn(r"\f[18]", new_text) - - def test_apply_font_size_prepends_when_missing(self): - text = "Plain text without font codes" - new_text, count = wolf_codes.apply_font_size(text, 18) - self.assertEqual(count, 1) - self.assertTrue(new_text.startswith(r"\f[18]")) - - def test_infer_base_font_prefers_body_over_emphasis(self): - text = r"So they even take worries\nlike this in the\n\c[21]\f[20]confessional\c[19]\f[18]..." self.assertEqual(wolf_codes.infer_base_font_size(text), 18) - def test_scale_font_sizes_keeps_emphasis_ratio(self): - text = ( - r"So they even take worries\nlike this in the\n" - r"\c[21]\f[20]confessional\c[19]\f[18]..." - ) - new_text, count = wolf_codes.scale_font_sizes(text, 14) - self.assertEqual(count, 3) # 2 scaled + leading body insert - # 20 * 14/18 ≈ 15.56 → 16; 18 * 14/18 = 14 - self.assertTrue(new_text.startswith(r"\f[14]")) - self.assertIn(r"\f[16]", new_text) - self.assertIn(r"\f[14]", new_text) - self.assertNotIn(r"\f[20]", new_text) - self.assertNotIn(r"\f[18]", new_text) - - def test_scale_font_sizes_matches_names_wrap_style_shrink(self): - source_style = r"\c[21]\f[20]Brothel\c[19]\f[18] doesn't get much use" - new_text, _ = wolf_codes.scale_font_sizes(source_style, 17) - # 20*17/18 ≈ 18.89 → 19; body 17 - self.assertTrue(new_text.startswith(r"\f[17]")) - self.assertIn(r"\f[19]", new_text) - self.assertIn(r"\f[17]", new_text) - - def test_scale_font_sizes_leads_with_body_for_midline_emphasis(self): - # Cafe-style: body only appears after emphasis; Wolf needs \\f at start. - text = ( - '"That \\c[21]\\f[14]cafe\\c[19]\\f[13] over there has been\n' - 'packed lately." "Apparently\n' - "they're hiring waitresses.\"" - ) - new_text, count = wolf_codes.scale_font_sizes(text, 13) - self.assertGreaterEqual(count, 2) - self.assertTrue(new_text.startswith(r"\f[13]")) - self.assertIn(r"\c[21]\f[14]cafe\c[19]\f[13]", new_text) - - def test_ensure_leading_font_size_idempotent(self): - text = r'\f[13]"That \c[21]\f[14]cafe\c[19]\f[13] over there"' - self.assertEqual(wolf_codes.ensure_leading_font_size(text, 13), text) - - def test_apply_font_size_also_leads(self): - text = r'"That \c[21]\f[20]cafe\c[19]\f[18] over there"' - new_text, _ = wolf_codes.apply_font_size(text, 13) - self.assertTrue(new_text.startswith(r"\f[13]")) - self.assertNotIn(r"\f[20]", new_text) - - def test_font_size_codes_differ_detects_names_wrap_shrink(self): - source = ( - r"「おい!例の\c[21]\f[20]教会の特別奉仕活動\c[19]\f[18]があるってよ!」" - ) - text = ( - r'\f[14]"Hey! Apparently there\'s one of those ' - r'\c[21]\f[16]Special Service Activities at the church\c[19]\f[14]!"' - ) - self.assertTrue(wolf_codes.font_size_codes_differ(source, text)) - - def test_font_size_codes_differ_false_when_color_missing(self): + def test_font_size_codes_differ_only_for_size_drift(self): source = r"\c[21]\f[20]娼館\c[19]\f[18]" - text = r"\f[14]Brothel" # dropped colour codes - self.assertFalse(wolf_codes.font_size_codes_differ(source, text)) - - def test_names_doc_has_font_size_drift(self): - doc = { - "kind": "names", - "names": [ - { - "source": r"\c[21]\f[20]娼館\c[19]\f[18]", - "text": r"\f[14]\c[21]\f[16]Brothel\c[19]\f[14]", - } - ], - } - self.assertTrue(wolf_codes.names_doc_has_font_size_drift(doc)) + shrunk = r"\f[14]\c[21]\f[16]Brothel\c[19]\f[14]" + dropped_color = r"\f[14]Brothel" + self.assertTrue(wolf_codes.font_size_codes_differ(source, shrunk)) + self.assertFalse(wolf_codes.font_size_codes_differ(source, dropped_color)) + self.assertTrue( + wolf_codes.names_doc_has_font_size_drift( + {"kind": "names", "names": [{"source": source, "text": shrunk}]} + ) + ) if __name__ == "__main__": diff --git a/tests/test_wolf_names.py b/tests/test_wolf_names.py index 5bd806d..bdace4a 100644 --- a/tests/test_wolf_names.py +++ b/tests/test_wolf_names.py @@ -137,34 +137,14 @@ class TestNameWrapRoles(unittest.TestCase): def tearDown(self): self._tmp.cleanup() - def test_roles_path_beside_names(self): - self.assertEqual( - wn.roles_json_path_for_names(self.names), - self.dir / "wolfdawn-roles.json", - ) - - def test_upsert_and_load_name_wrap_role(self): + def test_upsert_load_and_update_name_wrap_role(self): roles_path = wn.roles_json_path_for_names(self.names) - wn.upsert_name_wrap_role(roles_path, "説明", width=48, max_lines=3) - roles = wn.load_name_wrap_roles(roles_path) - role = wn.get_name_wrap_role(roles, "説明") - self.assertIsNotNone(role) - assert role is not None - self.assertEqual(role["width"], 48) - self.assertEqual(role["maxLines"], 3) - - def test_upsert_updates_existing_note(self): - roles_path = wn.roles_json_path_for_names(self.names) - wn.upsert_name_wrap_role(roles_path, "説明", width=40) + self.assertEqual(roles_path, self.dir / "wolfdawn-roles.json") + wn.upsert_name_wrap_role(roles_path, "説明", width=40, font=14) wn.upsert_name_wrap_role(roles_path, "説明", width=52, max_lines=4) role = wn.get_name_wrap_role(wn.load_name_wrap_roles(roles_path), "説明") self.assertEqual(role["width"], 52) self.assertEqual(role["maxLines"], 4) - - def test_upsert_persists_font(self): - roles_path = wn.roles_json_path_for_names(self.names) - wn.upsert_name_wrap_role(roles_path, "説明", width=40, font=14) - role = wn.get_name_wrap_role(wn.load_name_wrap_roles(roles_path), "説明") self.assertEqual(role["font"], 14) diff --git a/tests/test_wolf_wrap_search.py b/tests/test_wolf_wrap_search.py index 0b2a677..6b536e6 100644 --- a/tests/test_wolf_wrap_search.py +++ b/tests/test_wolf_wrap_search.py @@ -53,21 +53,6 @@ RUMOR_FIXTURE = { } -class TestWolfVisibleLength(unittest.TestCase): - def test_font_codes_do_not_count_toward_width(self): - plain = "Hello world" - coded = r"Hello \f[2]world" - self.assertEqual( - dazedwrap.max_line_visible_length(plain), - dazedwrap.max_line_visible_length(coded), - ) - - def test_gatekeeper_line_is_overflow_at_bulletin_width(self): - length = dazedwrap.max_line_visible_length(GATEKEEPER_TEXT) - self.assertGreater(length, 40) - self.assertGreater(length, 34) - - class TestWrapSearch(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() @@ -81,172 +66,29 @@ class TestWrapSearch(unittest.TestCase): def tearDown(self): self.tmp.cleanup() - def test_search_finds_gatekeeper_in_rumor_sheet(self): + def test_search_and_wrap_db_sheet(self): hits = ws.search_translated_text("gatekeeper just randomly", self.translated) self.assertEqual(len(hits), 1) hit = hits[0] - self.assertEqual(hit.kind, "db") self.assertEqual(hit.sheet_name, RUMOR_SHEET) self.assertEqual(hit.row, GATEKEEPER_ROW) - self.assertEqual(hit.field_name, GATEKEEPER_FIELD) - self.assertEqual(hit.json_file, "DataBase.project.json") - self.assertIn("gatekeeper", hit.text.lower()) - def test_search_finds_horned_rabbits_same_row(self): - hits = ws.search_translated_text("horned rabbit", self.translated) - self.assertEqual(len(hits), 1) - self.assertEqual(hits[0].row, GATEKEEPER_ROW) - - def test_wrap_overflow_in_sheet_changes_long_lines(self): path = self.translated / "DataBase.project.json" doc = json.loads(path.read_text(encoding="utf-8")) changed = ws.wrap_overflow_in_sheet(path, doc, RUMOR_SHEET, width=34) self.assertEqual(changed, 1) - doc2 = json.loads(path.read_text(encoding="utf-8")) - line = ws.locate_line( - doc2, - { - "kind": "db", - "sheet_name": RUMOR_SHEET, - "row": GATEKEEPER_ROW, - "field_name": GATEKEEPER_FIELD, - }, - ) - self.assertIsNotNone(line) + line = ws.locate_line(doc, hit.hit_id) assert line is not None self.assertIn("\n", line["text"]) self.assertLessEqual(dazedwrap.max_line_visible_length(line["text"]), 34) - def test_wrap_profile_remembers_sheet_width(self): ws.set_sheet_width(self.work, RUMOR_SHEET, 34, json_file="DataBase.project.json") - profile = ws.load_wrap_profile(self.work) - self.assertEqual(ws.get_sheet_width(profile, RUMOR_SHEET), 34) - entry = profile["sheets"][RUMOR_SHEET] - self.assertEqual(entry["json_file"], "DataBase.project.json") - - def test_sheet_overflow_summary_counts_gatekeeper_row(self): - summaries = ws.sheet_overflow_summaries(self.translated, width=34) - rumor = [s for s in summaries if s.sheet_name == RUMOR_SHEET] - self.assertEqual(len(rumor), 1) - self.assertEqual(rumor[0].overflow_count, 1) - self.assertEqual(rumor[0].line_count, 2) - - -class TestNamesSearch(unittest.TestCase): - def setUp(self): - self.tmp = tempfile.TemporaryDirectory() - self.translated = Path(self.tmp.name) / "translated" - self.translated.mkdir() - fixture = { - "kind": "names", - "count": 1, - "names": [ - { - "source": "ブロthel scout line", - "text": ( - "Maybe I'll go scout out some newcomers at the brothel.\n" - "\u3000Hope there's a pure-looking girl with big boobs..." - ), - "note": "Profile · プロフィール", - "safety": "safe", - } - ], - } - (self.translated / "names.json").write_text( - json.dumps(fixture, ensure_ascii=False, indent=4), - encoding="utf-8", + self.assertEqual( + ws.get_sheet_width(ws.load_wrap_profile(self.work), RUMOR_SHEET), 34 ) - def tearDown(self): - self.tmp.cleanup() - def test_search_finds_names_json_profile_text(self): - hits = ws.search_translated_text("there's a pure", self.translated) - self.assertEqual(len(hits), 1) - hit = hits[0] - self.assertEqual(hit.kind, "names") - self.assertEqual(hit.json_file, "names.json") - self.assertIn("pure-looking", hit.text) - - def test_load_hit_from_names_index(self): - hits = ws.search_translated_text("pure-looking", self.translated) - self.assertEqual(len(hits), 1) - path, doc, line = ws.load_hit_from_id(self.translated, hits[0].hit_id) - self.assertIsNotNone(path) - assert path is not None and line is not None - self.assertEqual(path.name, "names.json") - self.assertIn("pure-looking", line["text"]) - - -class TestWrapPreview(unittest.TestCase): - SAMPLE = ( - '"Apparently there\'s a super hot female adventurer who just ' - 'came to town!? Maybe I can meet her if I head to the ' - r'\c[21]\f[20]tavern\c[19]\f[18]!"' - ) - - def test_preview_detects_wrap_needed(self): - info = ws.wrap_preview_info(self.SAMPLE, 40) - self.assertTrue(info["needs_wrap"]) - self.assertGreater(info["longest"], 40) - self.assertGreater(info["output_line_count"], 1) - - def test_format_preview_shows_line_counts(self): - preview = ws.format_wrap_preview(self.SAMPLE, 40) - self.assertIn("(40)", preview) - self.assertIn("Apparently", preview) - - def test_split_line_at_visible_width(self): - line = "Apparently there's a super hot female adventurer who just" - fit_end, line_len = ws.split_line_at_visible_width(line, 40) - self.assertEqual(line_len, len(line)) - self.assertGreater(fit_end, 0) - self.assertLess(fit_end, line_len) - self.assertLessEqual( - dazedwrap.max_line_visible_length(line[:fit_end]), - 40, - ) - - def test_codes_do_not_inflate_visible_counts(self): - text = r'See the \c[21]\f[20]tavern\c[19]\f[18] on Main Street' - info = ws.wrap_preview_info(text, 30) - self.assertLess(info["longest"], len(text)) - self.assertLessEqual(info["longest"], 30 + 10) - - def test_render_wolf_text_html_hides_codes_and_styles(self): - text = r'That \c[21]\f[20]cafe\c[19]\f[18] over there' - html = ws.render_wolf_text_html(text, default_font_px=18) - self.assertNotIn(r"\c[", html) - self.assertNotIn(r"\f[", html) - self.assertIn("cafe", html) - self.assertIn("font-size:20px", html) - self.assertIn("font-size:18px", html) - # Emphasis colour for \\c[21] - self.assertIn(ws.wolf_preview_color(21), html) - - def test_format_wrap_preview_html_includes_gutters(self): - text = ( - r'\f[18]"That \c[21]\f[20]cafe\c[19]\f[18] over there has been' - '\npacked lately."' - ) - html = ws.format_wrap_preview_html(text, 40) - self.assertIn("(37)", html) - self.assertIn("cafe", html) - self.assertNotIn(r"\f[18]", html) - self.assertIn("font-size:20px", html) - self.assertIn(ws.wolf_preview_color(21), html) - - def test_format_wrap_preview_html_simulates_body_font(self): - text = r'That \c[21]\f[20]cafe\c[19]\f[18] over there' - html = ws.format_wrap_preview_html(text, 40, body_font=13) - # 20*13/18 ≈ 14; body 13 - self.assertIn("font-size:14px", html) - self.assertIn("font-size:13px", html) - self.assertNotIn("font-size:20px", html) - self.assertNotIn("font-size:18px", html) - - -class TestNamesCategoryWrap(unittest.TestCase): +class TestNamesSearchAndWrap(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.translated = Path(self.tmp.name) / "translated" @@ -254,11 +96,10 @@ class TestNamesCategoryWrap(unittest.TestCase): self.category = "├■街の噂(MOB)" fixture = { "kind": "names", - "count": 2, "names": [ { - "source": "短い", - "text": "Short line.", + "source": "「メイドがどんどん辞めていく」", + "text": '"Yeah, maids keep quitting one after another."', "note": self.category, }, { @@ -277,107 +118,44 @@ class TestNamesCategoryWrap(unittest.TestCase): def tearDown(self): self.tmp.cleanup() - def test_names_categories_in_sheet_summaries(self): - summaries = ws.sheet_overflow_summaries(self.translated, width=34) - names_rows = [s for s in summaries if s.kind == "names" and s.sheet_name == self.category] - self.assertEqual(len(names_rows), 1) - self.assertEqual(names_rows[0].line_count, 2) - self.assertEqual(names_rows[0].overflow_count, 1) + def test_search_summary_shows_translated_text(self): + hits = ws.search_translated_text("maids", self.translated) + self.assertEqual(len(hits), 1) + summary = hits[0].summary(40) + self.assertTrue(summary.split("\n", 1)[0].startswith('"Yeah, maids')) + self.assertNotIn("メイド", summary) + path, _doc, line = ws.load_hit_from_id(self.translated, hits[0].hit_id) + assert path is not None and line is not None + self.assertEqual(path.name, "names.json") + self.assertIn("maids", line["text"]) - def test_wrap_all_in_names_category(self): + def test_wrap_overflow_in_names_category(self): doc = json.loads(self.path.read_text(encoding="utf-8")) changed = ws.wrap_overflow_in_sheet( self.path, doc, self.category, 34, kind="names", ) - self.assertEqual(changed, 1) - doc2 = json.loads(self.path.read_text(encoding="utf-8")) - long_entry = doc2["names"][1]["text"] - self.assertIn("\n", long_entry) - - def test_names_search_summary_shows_translated_text(self): - hits = ws.search_translated_text("maids", self.translated) - # Seed a maid line so EN query matches EN text, not JP source. - doc = json.loads(self.path.read_text(encoding="utf-8")) - doc["names"].append( - { - "source": "「メイドがどんどん辞めていく」", - "text": '"Yeah, maids keep quitting one after another."', - "note": self.category, - } - ) - self.path.write_text(json.dumps(doc, ensure_ascii=False, indent=4), encoding="utf-8") - hits = ws.search_translated_text("maids", self.translated) - self.assertEqual(len(hits), 1) - summary = hits[0].summary(40) - self.assertIn("maids", summary) - self.assertNotIn("メイド", summary) - self.assertIn("entry #2", summary) - # Translated preview is the first line of the summary. - self.assertIn("\n", summary) - self.assertTrue(summary.split("\n", 1)[0].strip().startswith('"Yeah')) + self.assertGreaterEqual(changed, 1) + long_text = json.loads(self.path.read_text(encoding="utf-8"))["names"][1]["text"] + self.assertIn("\n", long_text) + self.assertLessEqual(dazedwrap.max_line_visible_length(long_text), 34) -class TestEventScopeWrap(unittest.TestCase): - def setUp(self): - self.tmp = tempfile.TemporaryDirectory() - self.translated = Path(self.tmp.name) / "translated" - self.translated.mkdir() - self.path = self.translated / "Map001.mps.json" - self.path.write_text( - json.dumps( - { - "kind": "map", - "file": "Map001.mps", - "scenes": [ - { - "event": 1, - "lines": [ - { - "text": ( - "This is a very long line of dialogue that " - "should wrap at thirty four visible chars." - ), - }, - {"text": "Short line."}, - ], - } - ], - }, - ensure_ascii=False, - indent=4, - ), - encoding="utf-8", - ) - self.hit = ws.WrapHit( - json_file="Map001.mps.json", - kind="map", - sheet_name="Map001.mps", - row=1, - field_name="scene 0 line 0", - text="very long line", - max_line_len=80, - scene_index=0, - line_index=0, - map_file="Map001.mps", - ) - - def tearDown(self): - self.tmp.cleanup() - - def test_scope_stats_for_map_file(self): - doc = json.loads(self.path.read_text(encoding="utf-8")) - stats = ws.scope_stats(doc, self.hit, 34) - self.assertEqual(stats.total, 2) - self.assertEqual(stats.overflow, 1) - - def test_wrap_all_in_map_file(self): - doc = json.loads(self.path.read_text(encoding="utf-8")) - changed = ws.wrap_overflow_in_scope(self.path, doc, self.hit, 34) - self.assertEqual(changed, 1) +class TestWrapPreviewHtml(unittest.TestCase): + def test_html_preview_hides_codes_and_simulates_body_font(self): + text = r'That \c[21]\f[20]cafe\c[19]\f[18] over there' + html = ws.format_wrap_preview_html(text, 40, body_font=13) + self.assertNotIn(r"\c[", html) + self.assertNotIn(r"\f[", html) + self.assertIn("cafe", html) + self.assertIn("font-size:14px", html) # 20*13/18 + self.assertIn("font-size:13px", html) + self.assertIn(ws.wolf_preview_color(21), html) + info = ws.wrap_preview_info(text, 40) + self.assertLess(info["longest"], len(text)) class TestManualFontAndWrap(unittest.TestCase): - def test_scales_emphasis_then_wraps(self): + def test_manual_scales_emphasis_wraps_and_skips_short_plain(self): text = ( r"So they even take worries like this in the " r"\c[21]\f[20]confessional\c[19]\f[18] booth every day." @@ -388,18 +166,15 @@ class TestManualFontAndWrap(unittest.TestCase): self.assertIn(r"\f[14]", new_text) self.assertLessEqual(dazedwrap.max_line_visible_length(new_text), 40) - def test_group_manual_skips_short_plain_lines(self): - text = "Short." - new_text, changed = ws.apply_manual_font_and_wrap( - text, 40, font=14, only_overflow_or_fonts=True + short, skipped = ws.apply_manual_font_and_wrap( + "Short.", 40, font=14, only_overflow_or_fonts=True ) - self.assertFalse(changed) - self.assertEqual(new_text, text) + self.assertFalse(skipped) + self.assertEqual(short, "Short.") def test_wrap_overflow_manual_in_names_category(self): - tmp = tempfile.TemporaryDirectory() - try: - path = Path(tmp.name) / "names.json" + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "names.json" doc = { "kind": "names", "names": [ @@ -428,12 +203,48 @@ class TestManualFontAndWrap(unittest.TestCase): changed = ws.wrap_overflow_manual_in_scope(path, doc, hit, 36, font=14) self.assertEqual(changed, 1) saved = json.loads(path.read_text(encoding="utf-8")) - out = saved["names"][0]["text"] - self.assertIn(r"\f[14]", out) + self.assertIn(r"\f[14]", saved["names"][0]["text"]) self.assertEqual(saved["names"][1]["text"], "OK") self.assertEqual(saved["names"][2]["text"], "x" * 80) - finally: - tmp.cleanup() + + +class TestEventScopeWrap(unittest.TestCase): + def test_wrap_all_in_map_file(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "Map001.mps.json" + doc = { + "kind": "map", + "file": "Map001.mps", + "scenes": [ + { + "event": 1, + "lines": [ + { + "text": ( + "This is a very long line of dialogue that " + "should wrap at thirty four visible chars." + ), + }, + {"text": "Short line."}, + ], + } + ], + } + path.write_text(json.dumps(doc, ensure_ascii=False), encoding="utf-8") + hit = ws.WrapHit( + json_file="Map001.mps.json", + kind="map", + sheet_name="Map001.mps", + row=1, + field_name="scene 0 line 0", + text="very long line", + max_line_len=80, + scene_index=0, + line_index=0, + map_file="Map001.mps", + ) + self.assertEqual(ws.scope_stats(doc, hit, 34).overflow, 1) + self.assertEqual(ws.wrap_overflow_in_scope(path, doc, hit, 34), 1) if __name__ == "__main__": diff --git a/tests/test_wolfdawn_relayout.py b/tests/test_wolfdawn_relayout.py index fc37aed..6970adf 100644 --- a/tests/test_wolfdawn_relayout.py +++ b/tests/test_wolfdawn_relayout.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Unit tests for WolfDawn relayout / desc_relayout CLI wrappers.""" +"""Unit tests for WolfDawn relayout / names-wrap CLI wrappers.""" from __future__ import annotations @@ -17,7 +17,7 @@ from util import wolfdawn # noqa: E402 class TestRelayoutWrappers(unittest.TestCase): - def test_relayout_dry_run_args(self): + def test_relayout_auto_metrics(self): captured = {} def fake_run(args, log_fn=None): @@ -25,40 +25,18 @@ class TestRelayoutWrappers(unittest.TestCase): return wolfdawn.WolfResult(0, "", "", [str(a) for a in args]) with patch.object(wolfdawn, "_run", side_effect=fake_run): - wolfdawn.relayout("/game/Data", width=55, max_rows=0) - self.assertEqual(captured["args"][:2], ["relayout", "/game/Data"]) - self.assertIn("--width", captured["args"]) - self.assertIn("55", captured["args"]) - self.assertNotIn("-o", captured["args"]) - self.assertIn("--max-rows", captured["args"]) - self.assertIn("auto", captured["args"]) - - def test_relayout_write_args(self): - captured = {} - - def fake_run(args, log_fn=None): - captured["args"] = list(args) - return wolfdawn.WolfResult(0, "", "", [str(a) for a in args]) - - with patch.object(wolfdawn, "_run", side_effect=fake_run): - wolfdawn.relayout( - "/game/Data", - out_dir="/tmp/out", - width=60, - max_rows=4, - no_resolve=True, - ) + wolfdawn.relayout("/game/Data", width="auto", max_rows=0, out_dir="/tmp/out") args = captured["args"] - self.assertEqual(args[0], "relayout") + self.assertEqual(args[:2], ["relayout", "/game/Data"]) self.assertIn("-o", args) self.assertIn("/tmp/out", args) self.assertIn("--width", args) - self.assertIn("60", args) + self.assertIn("auto", args) self.assertIn("--max-rows", args) - self.assertIn("4", args) - self.assertIn("--no-resolve", args) + # max_rows=0 becomes auto + self.assertEqual(args[args.index("--max-rows") + 1], "auto") - def test_desc_relayout_args(self): + def test_desc_relayout_auto_max_lines(self): captured = {} def fake_run(args, log_fn=None): @@ -75,88 +53,32 @@ class TestRelayoutWrappers(unittest.TestCase): ) args = captured["args"] self.assertEqual(args[0], "desc-relayout") - self.assertIn("-o", args) - self.assertIn("/tmp/DataBase.project", args) - self.assertIn("--width", args) - self.assertIn("75", args) self.assertIn("--max-lines", args) - self.assertIn("auto", args) + self.assertEqual(args[args.index("--max-lines") + 1], "auto") self.assertIn("--keep-breaks", args) - def test_relayout_auto_width(self): - captured = {} - - def fake_run(args, log_fn=None): - captured["args"] = list(args) - return wolfdawn.WolfResult(0, "", "", [str(a) for a in args]) - - with patch.object(wolfdawn, "_run", side_effect=fake_run): - wolfdawn.relayout("/game/Data", width="auto", max_rows="auto") - args = captured["args"] - self.assertIn("--width", args) - self.assertIn("auto", args) - self.assertIn("--max-rows", args) - - def test_names_wrap_args(self): + def test_names_wrap_flags_and_parsers(self): captured = {} def fake_run(args, log_fn=None): captured["args"] = list(args) return wolfdawn.WolfResult( 0, - "names-wrap: 3 entry(ies) re-wrapped", + ' shrunk to \\f[14] to fit its slot: "Hey! ..."\n' + ' shrunk to \\f[12] to fit its slot: "That sister..."\n' + "names-wrap: 7 entry(ies) re-wrapped", "", [str(a) for a in args], ) with patch.object(wolfdawn, "_run", side_effect=fake_run): - res = wolfdawn.names_wrap("/tmp/names.json") - self.assertEqual(captured["args"], ["names-wrap", "/tmp/names.json"]) - self.assertEqual(wolfdawn.parse_names_wrap_counts(res.stdout), 3) - - def test_names_wrap_dry_run(self): - captured = {} - - def fake_run(args, log_fn=None): - captured["args"] = list(args) - return wolfdawn.WolfResult( - 0, - "names-wrap: 2 entry(ies) WOULD be re-wrapped", - "", - [str(a) for a in args], - ) - - with patch.object(wolfdawn, "_run", side_effect=fake_run): - wolfdawn.names_wrap("/tmp/names.json", dry_run=True) - self.assertEqual(captured["args"], ["names-wrap", "/tmp/names.json", "--dry-run"]) - - def test_names_wrap_note_and_width(self): - captured = {} - - def fake_run(args, log_fn=None): - captured["args"] = list(args) - return wolfdawn.WolfResult(0, "", "", [str(a) for a in args]) - - with patch.object(wolfdawn, "_run", side_effect=fake_run): - wolfdawn.names_wrap( + res = wolfdawn.names_wrap( "/tmp/names.json", - note="├■街の噂(MOB)", - width=66, + note="説明", + width=50, + lines=3, + dry_run=True, ) - self.assertEqual( - captured["args"], - ["names-wrap", "/tmp/names.json", "--width", "66", "--note", "├■街の噂(MOB)"], - ) - - def test_names_wrap_lines(self): - captured = {} - - def fake_run(args, log_fn=None): - captured["args"] = list(args) - return wolfdawn.WolfResult(0, "", "", [str(a) for a in args]) - - with patch.object(wolfdawn, "_run", side_effect=fake_run): - wolfdawn.names_wrap("/tmp/names.json", width=50, lines=3, note="説明") self.assertEqual( captured["args"], [ @@ -168,16 +90,11 @@ class TestRelayoutWrappers(unittest.TestCase): "3", "--note", "説明", + "--dry-run", ], ) - - def test_parse_names_wrap_shrink_count(self): - blob = ( - ' shrunk to \\f[14] to fit its slot: "Hey! ..."\n' - ' shrunk to \\f[12] to fit its slot: "That sister..."\n' - "names-wrap: 7 entry(ies) re-wrapped" - ) - self.assertEqual(wolfdawn.parse_names_wrap_shrink_count(blob), 2) + self.assertEqual(wolfdawn.parse_names_wrap_counts(res.stdout), 7) + self.assertEqual(wolfdawn.parse_names_wrap_shrink_count(res.stdout), 2) if __name__ == "__main__": diff --git a/util/wolfdawn/codes.py b/util/wolfdawn/codes.py index e454d24..194c58f 100644 --- a/util/wolfdawn/codes.py +++ b/util/wolfdawn/codes.py @@ -134,27 +134,6 @@ def scale_font_sizes( return new_text, count -def apply_font_size(text: str, size: int) -> tuple[str, int]: - """Force every ``\\f[N]`` in *text* to the same *size* (no proportional scale). - - Prefer :func:`scale_font_sizes` when the line has emphasis overrides that - should stay relatively larger than the body font. - - Always ensures a leading ``\\f[size]``. Returns ``(new_text, replacements)``. - """ - if not isinstance(text, str) or not text.strip() or size <= 0: - return text, 0 - size = int(size) - matches = list(WOLF_FONT_SIZE_RE.finditer(text)) - if matches: - had_leading = bool(WOLF_FONT_SIZE_RE.match(text)) - new_text = WOLF_FONT_SIZE_RE.sub(rf"\\f[{size}]", text) - new_text = ensure_leading_font_size(new_text, size) - count = len(matches) + (0 if had_leading else 1) - return new_text, count - return ensure_leading_font_size(text, size), 1 - - def _control_code_tokens(text: str) -> list[str]: """Return the sequence of WolfDawn-compared control codes in *text*.""" if not isinstance(text, str) or not text: diff --git a/util/wolfdawn/wrap_search.py b/util/wolfdawn/wrap_search.py index ee1205f..296704b 100644 --- a/util/wolfdawn/wrap_search.py +++ b/util/wolfdawn/wrap_search.py @@ -9,12 +9,11 @@ from __future__ import annotations import json import re -from dataclasses import dataclass, field +from dataclasses import dataclass from pathlib import Path from typing import Any, Sequence import util.dazedwrap as dazedwrap -from util.wolfdawn.db_classify import group_key, json_file_from_doc from util.wolfdawn.selective_wrap import line_needs_wrap, wrap_line_text WRAP_PROFILE_NAME = "wrap_profile.json" @@ -89,22 +88,6 @@ class WrapHit: ) -@dataclass -class SheetOverflowSummary: - json_file: str - sheet_name: str - line_count: int - overflow_count: int - tier: str = "" - kind: str = "db" - - @property - def key(self) -> str: - if self.kind == "names": - return f"{self.json_file}|names|{self.sheet_name}" - return group_key(self.json_file, self.sheet_name) - - def wrap_profile_path(work_dir: str | Path) -> Path: return Path(work_dir) / WRAP_PROFILE_NAME @@ -402,20 +385,6 @@ def wrap_preview_info(text: str, width: int) -> dict[str, Any]: } -def format_wrap_preview(text: str, width: int) -> str: - """Multiline preview with per-line visible character counts.""" - info = wrap_preview_info(text, width) - if not info["wrapped"]: - return "" - lines = info["wrapped"].replace("\r\n", "\n").replace("\r", "\n").split("\n") - parts: list[str] = [] - for i, line in enumerate(lines): - vis = dazedwrap.max_line_visible_length(line) - marker = "⚠" if vis > width else " " - parts.append(f"{marker} {i + 1:2d} ({vis:2d}) {line}") - return "\n".join(parts) - - def wrap_preview_summary(text: str, width: int) -> str: """One-line status for the preview header.""" info = wrap_preview_info(text, width) @@ -578,15 +547,6 @@ def format_wrap_preview_html( ) -def split_line_at_visible_width(line: str, width: int) -> tuple[int, int]: - """Return ``(fit_end, line_len)`` char indices in *line* for UI highlighting.""" - line_len = len(line) - if not line or width <= 0: - return (line_len, line_len) - fit_end = dazedwrap.visible_word_wrap_end_index(line, width) - return (min(fit_end, line_len), line_len) - - def locate_line(doc: dict[str, Any], hit_id: dict[str, Any]) -> dict[str, Any] | None: """Return the live line dict for *hit_id* inside *doc*.""" kind = hit_id.get("kind") @@ -1081,96 +1041,3 @@ def _wrap_overflow_in_names_category( if changed: save_document(path, doc) return changed - - -def _names_category_summaries( - doc: dict[str, Any], - *, - json_file: str, - width: int, -) -> list[SheetOverflowSummary]: - counts: dict[str, int] = {} - overflow: dict[str, int] = {} - for entry in doc.get("names") or []: - if not isinstance(entry, dict): - continue - note = str(entry.get("note") or "names.json") - counts[note] = counts.get(note, 0) + 1 - text = entry.get("text") - if isinstance(text, str) and line_needs_wrap(text, width): - overflow[note] = overflow.get(note, 0) + 1 - return [ - SheetOverflowSummary( - json_file=json_file, - sheet_name=note, - line_count=counts[note], - overflow_count=overflow.get(note, 0), - tier="names", - kind="names", - ) - for note in sorted(counts) - ] - - -def sheet_overflow_summaries( - translated_dir: str | Path, - width: int, - *, - files_dir: str | Path | None = None, -) -> list[SheetOverflowSummary]: - """Per DB sheet overflow counts at *width*.""" - from util.wolfdawn.db_classify import analyze_content_distribution, classify_db_document - - base = Path(translated_dir) - if not base.is_dir(): - base = Path(files_dir) if files_dir else base - dist = analyze_content_distribution(base) - summaries: list[SheetOverflowSummary] = [] - - for path in sorted(base.glob("*.project.json")): - doc = _load_json(path) - if not doc or doc.get("kind") != "db": - continue - jf = path.name - for group in classify_db_document(doc, json_file=jf): - overflow = 0 - for g in doc.get("groups") or []: - if str(g.get("typeName") or "") != group.type_name: - continue - for line in g.get("lines") or []: - text = line.get("text") - if isinstance(text, str) and line_needs_wrap(text, width): - overflow += 1 - summaries.append( - SheetOverflowSummary( - json_file=jf, - sheet_name=group.type_name, - line_count=group.line_count, - overflow_count=overflow, - tier=group.tier, - ) - ) - if not summaries: - summaries = [ - SheetOverflowSummary( - json_file=g.json_file, - sheet_name=g.type_name, - line_count=g.line_count, - overflow_count=0, - tier=g.tier, - ) - for g in dist.groups - ] - - seen_names: set[Path] = set() - for search_base in _iter_search_dirs(base, Path(files_dir) if files_dir else None): - names_path = search_base / "names.json" - if not names_path.is_file() or names_path in seen_names: - continue - seen_names.add(names_path) - doc = _load_json(names_path) - if doc and doc.get("kind") == "names": - summaries.extend( - _names_category_summaries(doc, json_file="names.json", width=width) - ) - return summaries