diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71cdc9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Python 缓存与编译产物 +__pycache__/ +*.py[cod] +*$py.class + +# 测试与覆盖率产物 +tests/ +.pytest_cache/ +.coverage +.coverage.* +htmlcov/ + +# 类型检查与代码质量工具缓存 +.mypy_cache/ +.pyright/ +.ruff_cache/ + +# Python 虚拟环境 +.venv/ +venv/ +env/ + +# 编辑器与操作系统文件 +.idea/ +.vscode/ +.DS_Store +Thumbs.db + +# 本地日志和临时文件 +*.log +*.tmp +*.temp +*.bak +*.swp +*~ diff --git a/core/jx3box_data.py b/core/jx3box_data.py index 4211632..322c748 100644 --- a/core/jx3box_data.py +++ b/core/jx3box_data.py @@ -134,7 +134,7 @@ class JX3BOXService: params = { "per": "10", "page": "1", - "tags": "", + "tags": tags, "client": "std", "global_level": "130", "mount": mount, diff --git a/tests/test_template_system.py b/tests/test_template_system.py deleted file mode 100644 index 95b0cb5..0000000 --- a/tests/test_template_system.py +++ /dev/null @@ -1,267 +0,0 @@ -import asyncio -import re -import unittest -from pathlib import Path - -from jinja2 import ChainableUndefined, Environment - -from core.template import TemplateRepository - - -ROOT = Path(__file__).parent.parent -TEMPLATE_ROOT = ROOT / "templates" -SPECIAL_PAGE_STYLES = { - "baizhan", - "chengbeng", - "chengjiu", - "fubenjilu", - "helps", - "jiaoyihang", - "juesheliaotian", - "richangyuche", - "wujia", - "xingxiashijian", - "zhanji", - "zili", - "zilipaixing", -} - - -class TemplateRepositoryTests(unittest.TestCase): - def setUp(self): - self.repository = TemplateRepository(TEMPLATE_ROOT) - - def test_all_pages_compose_to_valid_jinja_documents(self): - pages = sorted((TEMPLATE_ROOT / "pages").glob("*.html")) - page_styles = sorted((TEMPLATE_ROOT / "styles" / "pages").glob("*.css")) - self.assertEqual(46, len(pages)) - self.assertEqual( - SPECIAL_PAGE_STYLES, - {style.stem for style in page_styles}, - ) - - environment = Environment(undefined=ChainableUndefined) - empty_context = { - "data": {}, - "icons": {"img": {}, "sect": {}, "serendipity": {}}, - "items": [], - "summary": {}, - } - for page in pages: - with self.subTest(page=page.name): - fragment = page.read_text(encoding="utf-8") - for document_tag in ("!DOCTYPE", "html", "head", "style", "body"): - self.assertIsNone( - re.search(rf"<{document_tag}(?:\s|>)", fragment, re.IGNORECASE) - ) - - template = asyncio.run(self.repository.get(page.name)) - self.assertIn("", template) - self.assertIn("", template) - self.assertNotIn("", template) - self.assertNotIn("/*__PAGE_CSS__*/", template) - self.assertNotIn("/* @component", template) - self.assertNotIn("/* @endcomponent */", template) - rendered = environment.from_string(template).render(empty_context) - self.assertIn("", rendered) - - def test_repeated_page_patterns_use_shared_components(self): - components = (TEMPLATE_ROOT / "styles" / "components.css").read_text( - encoding="utf-8" - ) - for selector in ( - ".data-table", - ".data-cell--rank", - ".card-grid", - ".ability-card", - ".collection-card", - ".object-card", - ): - self.assertIn(selector, components) - - expected_components = { - "bilei.html": "data-table", - "rank_role.html": "data-cell--rank", - "jineng.html": "ability-card", - "juesheqiyu.html": "collection-card", - "qiwu.html": "object-card", - } - for name, component in expected_components.items(): - with self.subTest(page=name): - fragment = (TEMPLATE_ROOT / "pages" / name).read_text( - encoding="utf-8" - ) - self.assertIn(component, fragment) - page_style = TEMPLATE_ROOT / "styles" / "pages" / ( - f"{Path(name).stem}.css" - ) - self.assertFalse(page_style.exists()) - - def test_image_templates_do_not_define_responsive_breakpoints(self): - for style in sorted((TEMPLATE_ROOT / "styles").rglob("*.css")): - with self.subTest(style=style.relative_to(TEMPLATE_ROOT)): - content = style.read_text(encoding="utf-8") - self.assertIsNone( - re.search(r"@media\s*(?:screen\s+and\s+)?\([^)]*width", content) - ) - - base = (TEMPLATE_ROOT / "styles" / "base.css").read_text(encoding="utf-8") - self.assertIn("min-width: calc(var(--jx3-page-width) + 94px)", base) - self.assertIn("width: var(--jx3-page-width)", base) - self.assertNotIn("100vw", base) - - def test_only_declared_component_blocks_are_injected(self): - table_page = asyncio.run(self.repository.get("bilei.html")) - self.assertIn(".data-table--spaced", table_page) - self.assertNotIn(".ability-card__head", table_page) - self.assertNotIn(".object-card__footer", table_page) - - ability_page = asyncio.run(self.repository.get("jineng.html")) - self.assertIn(".card-grid", ability_page) - self.assertIn(".ability-card__head", ability_page) - self.assertNotIn(".data-table--spaced", ability_page) - self.assertNotIn(".object-card__footer", ability_page) - - object_page = asyncio.run(self.repository.get("qiwu.html")) - self.assertIn(".object-card__footer", object_page) - self.assertNotIn(".ability-card__head", object_page) - - def test_wujia_groups_follow_the_api_names_instead_of_list_positions(self): - """万宝楼分组在真实返回中位于列表末尾,且在售组名为“在售期”。""" - template = asyncio.run(self.repository.get("wujia.html")) - rendered = Environment(undefined=ChainableUndefined).from_string(template).render( - { - "icons": {"img": {}, "sect": {}, "serendipity": {}}, - "name": "浮屠明音礼盒", - "alias": "", - "view": "", - "desc": "", - "category": "外观礼盒", - "date": "2018-08-24", - "retail": 888, - "list": [ - {"name": "电信区", "list": []}, - {"name": "双线区", "list": []}, - {"name": "无界区", "list": []}, - { - "name": "梦江南", - "list": [ - { - "date": "QUERY-MARKER", - "server": "梦江南", - "value": 4700, - "sale": 4, - } - ], - }, - {"name": "公示期", "list": []}, - { - "name": "在售期", - "list": [ - { - "date": "SALE-MARKER", - "zone": "电信区", - "server": "梦江南", - "value": 5188, - } - ], - }, - ], - } - ) - - sale_start = rendered.index('
') - query_start = rendered.index('
') - next_card = rendered.index('
', query_start) - sale_section = rendered[sale_start:query_start] - query_section = rendered[query_start:next_card] - - self.assertIn("SALE-MARKER", sale_section) - self.assertNotIn("QUERY-MARKER", sale_section) - self.assertIn("查询区服:梦江南", re.sub(r"\s+", "", query_section)) - self.assertIn("QUERY-MARKER", query_section) - self.assertNotIn("SALE-MARKER", query_section) - - def test_known_dark_theme_text_uses_shared_color_tokens(self): - expected = { - "jiaoyihang.css": (".item-name", ".price", ".sample"), - "juesheliaotian.css": (".role-col", ".message-col"), - "zilipaixing.css": (".force", ".role-name-col", ".server-col"), - } - for name, selectors in expected.items(): - with self.subTest(style=name): - content = (TEMPLATE_ROOT / "styles" / "pages" / name).read_text( - encoding="utf-8" - ) - for selector in selectors: - block = re.search( - rf"{re.escape(selector)}\s*\{{(?P.*?)\}}", - content, - re.DOTALL, - ) - self.assertIsNotNone(block) - self.assertIn("var(--jx3-", block.group("body")) - - def test_rejects_unknown_component_declaration(self): - components = (TEMPLATE_ROOT / "styles" / "components.css").read_text( - encoding="utf-8" - ) - with self.assertRaises(ValueError): - self.repository._select_component_styles(components, {"missing"}) - - def test_cache_returns_same_template_instance(self): - first = asyncio.run(self.repository.get("helps.html")) - shared_assets = self.repository._shared_assets - second = asyncio.run(self.repository.get("helps.html")) - self.assertIs(first, second) - asyncio.run(self.repository.get("bilei.html")) - self.assertIs(shared_assets, self.repository._shared_assets) - - self.repository.clear() - self.assertIsNone(self.repository._shared_assets) - - def test_rejects_path_traversal(self): - with self.assertRaises(ValueError): - asyncio.run(self.repository.get("../helps.html")) - - def test_help_page_catalogue_structure(self): - fragment = (TEMPLATE_ROOT / "pages" / "helps.html").read_text( - encoding="utf-8" - ) - categories = re.findall( - r'
([^<]+)
', fragment - ) - features = re.findall(r'
(.*?)', fragment) - - self.assertEqual( - [ - "活动情报", - "名剑相关", - "排行榜单", - "物价交易", - "阵营战场", - "角色名片", - "奇遇相关", - "百战记录", - "角色信息", - "资历相关", - "心法配装", - "游戏社区", - "百度贴吧", - "系统工具", - "杂项功能", - "避雷功能", - "推送功能", - ], - categories, - ) - self.assertEqual(69, len(features)) - self.assertEqual(105, len(commands)) - self.assertEqual(105, len(set(commands))) - self.assertIn("共 17 个功能分类 · 69 项功能 · 105 条指令", fragment) - - -if __name__ == "__main__": - unittest.main()