1 parent
d55640d37a
commit
fff8521a17
67 files changed
+11016
-16763
No files matched your search
@@ -0,0 +1,120 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
import sqlite3
|
||||
from html.parser import HTMLParser
|
||||
import tinycss2
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class IdCollector(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.ids = []
|
||||
|
||||
def handle_starttag(self, _tag, attrs):
|
||||
attrs = dict(attrs)
|
||||
if "id" in attrs:
|
||||
self.ids.append(attrs["id"])
|
||||
|
||||
|
||||
def js_bundle_source():
|
||||
js_dir = ROOT / "src/web/js"
|
||||
names = [line.strip() for line in (js_dir / "manifest.txt").read_text().splitlines() if line.strip() and not line.lstrip().startswith("#")]
|
||||
assert len(names) >= 10
|
||||
assert len(names) == len(set(names))
|
||||
return names, "\n".join((js_dir / name).read_text() for name in names)
|
||||
|
||||
|
||||
def test_schema_and_response_templates():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
migrations = sorted(ROOT.joinpath("migrations").glob("V*.sql"))
|
||||
for migration in migrations:
|
||||
conn.executescript(migration.read_text())
|
||||
assert int(migrations[-1].name[1:4]) == 45
|
||||
display_columns = {row[1] for row in conn.execute("pragma table_info(display_preferences)")}
|
||||
response_columns = {row[1] for row in conn.execute("pragma table_info(response_templates)")}
|
||||
assert "interface_skin" in display_columns
|
||||
assert "format" in response_columns
|
||||
conn.execute("insert into response_templates(name,body,format) values (?,?,?)", ("Text", "Hello", "text"))
|
||||
conn.execute("insert into response_templates(name,body,format) values (?,?,?)", ("HTML", "<b>Hello</b>", "html"))
|
||||
assert conn.execute("select count(*) from response_templates").fetchone()[0] == 2
|
||||
|
||||
|
||||
def test_html_contract():
|
||||
for relative in ["src/web/index.html", "ui/index.html"]:
|
||||
source = ROOT.joinpath(relative).read_text()
|
||||
parser = IdCollector()
|
||||
parser.feed(source)
|
||||
duplicates = {value for value in parser.ids if parser.ids.count(value) > 1}
|
||||
assert not duplicates
|
||||
assert "folder-drawer-header" in source
|
||||
assert "#icon-arrow-left" in source
|
||||
assert "pref-interface-skin" in source
|
||||
assert "compose-response-template" in source
|
||||
assert "data-appearance-preset" not in source
|
||||
for value in ["rustbox", "roundcube-classic", "roundcube-larry", "roundcube-elastic", "clean", "graphite", "high-contrast"]:
|
||||
assert f'value="{value}"' in source
|
||||
|
||||
|
||||
def test_roundcube_palette_contract():
|
||||
classic_elastic = (ROOT / "src/web/css/55-roundcube-skins.css").read_text().lower()
|
||||
extra_styles = (ROOT / "src/web/css/56-interface-styles.css").read_text().lower()
|
||||
for css in [classic_elastic, extra_styles]:
|
||||
errors = [item for item in tinycss2.parse_stylesheet(css, skip_comments=False, skip_whitespace=False) if item.type == "error"]
|
||||
assert not errors
|
||||
for token in ["#f6f6f6", "#f2f2f2", "#cc3333", "#929292", "#37beff", "#2f3a3f", "#21292c", "#c5d1d3", "#4d6066", "#374549"]:
|
||||
assert token in classic_elastic
|
||||
for selector in [
|
||||
'roundcube-classic"][data-theme="light',
|
||||
'roundcube-classic"][data-theme="dark',
|
||||
'roundcube-elastic"][data-theme="light',
|
||||
'roundcube-elastic"][data-theme="dark',
|
||||
]:
|
||||
assert selector in classic_elastic
|
||||
for selector in ["roundcube-larry", "clean", "graphite", "high-contrast"]:
|
||||
assert f'data-interface-skin="{selector}"' in extra_styles
|
||||
manifest = (ROOT / "src/web/css/manifest.txt").read_text()
|
||||
assert "55-roundcube-skins.css" in manifest
|
||||
assert "56-interface-styles.css" in manifest
|
||||
|
||||
|
||||
def test_modular_js_contract():
|
||||
assert not (ROOT / "src/web/app.js").exists()
|
||||
names, app = js_bundle_source()
|
||||
assert names[0] == "00-bootstrap.js"
|
||||
assert names[-1] == "95-bindings-boot.js"
|
||||
assert "interfaceStyleProfiles" in app
|
||||
assert '"roundcube-classic"' in app
|
||||
assert '"roundcube-larry"' in app
|
||||
assert '"roundcube-elastic"' in app
|
||||
assert "loadResponseTemplates" in app
|
||||
assert "insertSavedResponse" in app
|
||||
assert "appearancePresets" not in app
|
||||
assert "applyAppearancePreset" not in app
|
||||
assert "src/web/js/manifest.txt" in (ROOT / "README.md").read_text()
|
||||
build = (ROOT / "build.rs").read_text()
|
||||
assert 'root.join("src/web/js")' in build
|
||||
assert 'out.join("rustbox-ui.js")' in build
|
||||
assert 'include_bytes!(concat!(env!("OUT_DIR"), "/rustbox-ui.js"))' in (ROOT / "src/ui.rs").read_text()
|
||||
|
||||
|
||||
def test_backend_skin_contract_and_version():
|
||||
db_source = (ROOT / "src/db/display_preferences.rs").read_text()
|
||||
for value in ["rustbox", "roundcube-classic", "roundcube-larry", "roundcube-elastic", "clean", "graphite", "high-contrast"]:
|
||||
assert f'"{value}"' in db_source
|
||||
cargo = (ROOT / "Cargo.toml").read_text()
|
||||
assert 'version = "0.0.2"' in cargo
|
||||
assert "V044__interface_skin.sql" in (ROOT / "src/db/pool.rs").read_text()
|
||||
assert "V045__response_template_format.sql" in (ROOT / "src/db/pool.rs").read_text()
|
||||
for path in ROOT.glob("src/web/i18n/*.json"):
|
||||
json.loads(path.read_text())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_schema_and_response_templates()
|
||||
test_html_contract()
|
||||
test_roundcube_palette_contract()
|
||||
test_modular_js_contract()
|
||||
test_backend_skin_contract_and_version()
|
||||
print("roundcube-skin-contract: OK")
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html><html lang="en" data-theme="light" data-interface-skin="roundcube-elastic" data-density="compact" data-preview="right" data-scale="normal" data-color-preset="sage" data-font-family="system" data-contrast="normal" data-show-borders="true" data-toolbar-density="normal" data-unread-badge="dot" data-scrollbar-density="thin">
|
||||
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="stylesheet" href="../ui/app.css"><style>:root{--folder-pane-width:238px;--message-pane-width:430px;--row-height:52px;--sidebar-item-height:35px;--ui-size:14px;--control-height:30px;--appbar-height:44px;--icon-size:18px;--button-pad-x:10px;--button-pad-y:5px;--field-pad-x:8px;--field-pad-y:5px} .fixture-icon{width:18px;height:18px}</style></head>
|
||||
<body><div class="app-view">
|
||||
<header class="appbar"><div class="app-brand"><span class="brand-icon">✉</span><strong>RustBox</strong></div><div class="search-field"><span>⌕</span><input value="Search mail"><span></span><span>☷</span></div><div class="appbar-actions"><button class="icon-button">⚙</button><div class="account-summary"><span>linuxiarz@linuxiarz.pl</span><span>Connected</span></div></div></header>
|
||||
<div class="mail-shell"><button class="folder-drawer-scrim open"></button><aside class="folder-pane open">
|
||||
<div class="folder-drawer-header mobile-only"><button class="icon-button folder-drawer-close" aria-label="Back to messages"><svg viewBox="0 0 24 24"><path d="m15 18-6-6 6-6"/></svg></button><strong>Folders</strong></div>
|
||||
<button class="button primary compose-button">✎ <span>Compose</span></button><div class="section-heading"><span>Folders</span><span>+</span></div>
|
||||
<nav class="folder-list"><button class="folder-button active"><span>▣</span><span class="folder-name">INBOX</span><span class="folder-count unread">1424</span></button><button class="folder-button"><span>□</span><span class="folder-name">Archive</span><span></span></button><button class="folder-button"><span>□</span><span class="folder-name">Drafts</span><span></span></button><button class="folder-button"><span>□</span><span class="folder-name">Sent</span><span></span></button><button class="folder-button"><span>□</span><span class="folder-name">Spam</span><span></span></button><button class="folder-button"><span>□</span><span class="folder-name">Trash</span><span></span></button></nav>
|
||||
<div class="section-heading"><span>Labels</span><span>+</span></div><div class="folder-pane-footer"><span>IMAP</span><span>RustBox</span></div></aside>
|
||||
<section class="message-pane"><div class="pane-toolbar"><div class="pane-title"><div><strong>Inbox</strong><span>1,424 messages</span></div></div><div class="toolbar-actions"><button class="icon-button">↻</button></div></div><div class="message-list"><button class="message-row unread"><span class="message-state"></span><span class="message-main"><strong class="message-from">Roundcube Team</strong><span class="message-date">16:42</span></span><span class="message-subject-line"><span class="message-subject">Welcome to your inbox</span></span></button><button class="message-row active"><span class="message-state"></span><span class="message-main"><strong class="message-from">Example sender</strong><span class="message-date">15:17</span></span><span class="message-subject-line"><span class="message-subject">Selected message</span></span></button><button class="message-row"><span class="message-state"></span><span class="message-main"><strong class="message-from">Newsletter</strong><span class="message-date">Yesterday</span></span><span class="message-subject-line"><span class="message-subject">A regular message row</span></span></button></div></section>
|
||||
<section class="reader-pane"><div class="reader-toolbar"><button class="button quiet">Reply</button><button class="button quiet">Forward</button></div><article class="message-content"><header class="message-header"><h1>Selected message</h1><div class="message-meta"><div class="avatar">E</div><div class="sender"><strong>Example sender</strong><span>to linuxiarz</span></div></div></header><div class="message-body"><p>This fixture uses the real RustBox CSS bundle and component classes.</p><p><a href="#">Roundcube accent link</a></p><button class="button primary">Primary action</button></div></article></section>
|
||||
</div></div><script>const q=new URLSearchParams(location.search);document.documentElement.dataset.interfaceSkin=q.get('skin')||'roundcube-elastic';document.documentElement.dataset.theme=q.get('theme')||'light';</script></body></html>
|
||||
Reference in new issue
Block a user