11
This commit is contained in:
+35
@@ -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
|
||||||
|
*~
|
||||||
+1
-1
@@ -134,7 +134,7 @@ class JX3BOXService:
|
|||||||
params = {
|
params = {
|
||||||
"per": "10",
|
"per": "10",
|
||||||
"page": "1",
|
"page": "1",
|
||||||
"tags": "",
|
"tags": tags,
|
||||||
"client": "std",
|
"client": "std",
|
||||||
"global_level": "130",
|
"global_level": "130",
|
||||||
"mount": mount,
|
"mount": mount,
|
||||||
|
|||||||
@@ -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("<!DOCTYPE html>", template)
|
|
||||||
self.assertIn("<body", template)
|
|
||||||
self.assertIn("</body>", template)
|
|
||||||
self.assertNotIn("<!--__PAGE_CONTENT__-->", template)
|
|
||||||
self.assertNotIn("/*__PAGE_CSS__*/", template)
|
|
||||||
self.assertNotIn("/* @component", template)
|
|
||||||
self.assertNotIn("/* @endcomponent */", template)
|
|
||||||
rendered = environment.from_string(template).render(empty_context)
|
|
||||||
self.assertIn("</html>", 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('<div class="table-card sale">')
|
|
||||||
query_start = rendered.index('<div class="table-card query">')
|
|
||||||
next_card = rendered.index('<div class="table-card">', 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<body>.*?)\}}",
|
|
||||||
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'<div class="section-title">([^<]+)</div>', fragment
|
|
||||||
)
|
|
||||||
features = re.findall(r'<div class="feature(?:\s|\")', fragment)
|
|
||||||
commands = re.findall(r'<span class="command">(.*?)</span>', 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()
|
|
||||||
Reference in New Issue
Block a user