88 lines
6.3 KiB
Python
Executable File
88 lines
6.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Optional Chromium checks for Appearance controls and global corner geometry.
|
|
|
|
Uses shipped CSS and the real Appearance list-rendering function with mocked
|
|
preferences/translation state. Not a test of the authenticated Rust service.
|
|
"""
|
|
from pathlib import Path
|
|
import json
|
|
import shutil
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
WEB = ROOT / 'src/admin/web'
|
|
css = (WEB / 'style.css').read_text()
|
|
prefs = (WEB / 'app/preferences.js').read_text()
|
|
widgets = json.loads((WEB / 'dashboard-widgets.json').read_text())
|
|
translations = json.loads((ROOT / 'langs/en_US.json').read_text())['translations']
|
|
functions = prefs[:prefs.index('function browserLanguage()')] + '\n' + prefs[prefs.index('function dashboardWidgetLabel'):prefs.index('function refreshChartStylePalette')]
|
|
html = (f'<!doctype html><html data-theme="ultra_dark" data-corner-style="default"><head><style>{css}</style></head><body>'
|
|
'<div id="settings-page-appearance" class="settings-page" style="max-width:100%;padding:12px">'
|
|
'<div class="appearance-section"><div class="dashboard-widget-list" id="dashboard-widget-list"></div></div>'
|
|
'<div style="display:flex;gap:10px;margin-top:12px">'
|
|
'<div class="card" id="test-card">Card</div><div class="stat" id="test-stat">Stat</div>'
|
|
'<button class="outline" id="test-action">Action</button><input id="test-input" value="value"></div>'
|
|
'<a class="brand" href="#"><span class="brand-mark" id="test-logo">r<span>/</span>p</span>RustProxy</a></div>'
|
|
'<div class="overview-widget-type" id="editor-type-badge">Chart</div></body></html>')
|
|
js = f'''const ADMIN_DASHBOARD_WIDGET_REGISTRY = Object.freeze({json.dumps(widgets)});
|
|
const translations = {json.dumps(translations)};
|
|
function t(key) {{ return translations[key] || key; }}
|
|
function $(id) {{ return document.getElementById(id); }}
|
|
function el(tag, text, klass) {{ const node = document.createElement(tag); if (text != null) node.textContent = text; if (klass) node.className = klass; return node; }}
|
|
{functions}
|
|
renderDashboardWidgetSettings();'''
|
|
|
|
with sync_playwright() as playwright:
|
|
binary = shutil.which('chromium') or shutil.which('chromium-browser')
|
|
browser = playwright.chromium.launch(headless=True, executable_path=binary, args=['--no-sandbox'])
|
|
page = browser.new_page(viewport={'width': 1320, 'height': 950})
|
|
page.set_content(html, wait_until='domcontentloaded')
|
|
page.add_script_tag(content=js)
|
|
assert page.locator('#dashboard-widget-list .dashboard-widget-setting').count() == 62
|
|
assert page.locator('#settings-page-appearance .overview-widget-type').count() == 0
|
|
assert page.locator('#editor-type-badge').is_visible()
|
|
for width in (1320, 790, 450, 345):
|
|
page.set_viewport_size({'width': width, 'height': 950})
|
|
results = page.locator('.dashboard-widget-setting').evaluate_all('''rows => rows.map(row => {
|
|
const label = row.querySelector('.dashboard-widget-toggle').getBoundingClientRect();
|
|
const actions = row.querySelector('.dashboard-widget-setting-actions').getBoundingClientRect();
|
|
const box = row.getBoundingClientRect();
|
|
return { labelRight: label.right, actionsLeft: actions.left, actionsRight: actions.right, right: box.right };
|
|
})''')
|
|
for i, result in enumerate(results):
|
|
assert result['actionsLeft'] >= result['labelRight'] - 1, (width, i, result)
|
|
assert result['actionsRight'] <= result['right'] + 1, (width, i, result)
|
|
assert page.evaluate('document.documentElement.scrollWidth <= innerWidth + 2'), width
|
|
for theme in ('dark', 'light', 'ultra_dark', 'cream'):
|
|
page.evaluate('theme => { document.documentElement.dataset.theme = theme; document.documentElement.dataset.cornerStyle = "default"; }', theme)
|
|
stat = page.locator('#test-stat').evaluate('(n) => getComputedStyle(n).borderTopLeftRadius')
|
|
card = page.locator('#test-card').evaluate('(n) => getComputedStyle(n).borderTopLeftRadius')
|
|
assert stat == card == '0px', (theme, stat, card)
|
|
for style, expected_surface, expected_control in (('square', '0px', '0px'), ('subtle','5px','4px'), ('rounded','12px','8px'),('extra','18px','12px')):
|
|
page.evaluate('style => document.documentElement.dataset.cornerStyle = style', style)
|
|
assert page.locator('#test-stat').evaluate('(n) => getComputedStyle(n).borderTopLeftRadius') == expected_surface, (theme, style, 'stat')
|
|
assert page.locator('#test-card').evaluate('(n) => getComputedStyle(n).borderTopLeftRadius') == expected_surface, (theme, style, 'card')
|
|
assert page.locator('#test-action').evaluate('(n) => getComputedStyle(n).borderTopLeftRadius') == expected_control, (theme, style, 'button')
|
|
assert page.locator('#test-input').evaluate('(n) => getComputedStyle(n).borderTopLeftRadius') == expected_control, (theme, style, 'input')
|
|
page.evaluate('document.documentElement.dataset.theme = "dark"; document.documentElement.dataset.cornerStyle = "default"')
|
|
# The last glyph must have room before the outlined logo's right edge,
|
|
# including the 34px mobile variant; the slash-to-p spacing is unchanged.
|
|
for width in (1320, 790, 450, 345):
|
|
page.set_viewport_size({'width': width, 'height': 950})
|
|
inset = page.locator('#test-logo').evaluate('''mark => {
|
|
const p = [...mark.childNodes].find(node => node.nodeType === 3 && node.textContent.trim() === 'p');
|
|
const range = document.createRange();
|
|
range.selectNodeContents(p);
|
|
return mark.getBoundingClientRect().right - range.getBoundingClientRect().right;
|
|
}''')
|
|
assert inset >= 4, (width, inset)
|
|
page.set_viewport_size({'width': 1320, 'height': 950})
|
|
original_logo = page.locator('#test-logo').evaluate('(n) => getComputedStyle(n).borderTopColor')
|
|
page.evaluate('document.documentElement.dataset.theme = "ultra_dark"')
|
|
black_logo = page.locator('#test-logo').evaluate('(n) => getComputedStyle(n).borderTopColor')
|
|
assert black_logo == original_logo == 'rgb(246, 155, 116)', (black_logo, original_logo)
|
|
out = ROOT.parent / 'appearance_ux_0323_preview.png'
|
|
page.screenshot(path=str(out))
|
|
browser.close()
|
|
print('PASS: 62 badge-free Appearance rows, right-pinned buttons at 4 widths, theme-independent corner geometry, original black-theme logo accent')
|