v0.32.2
This commit is contained in:
1 parent
bb025dcf44
commit
b0f211555a
74 files changed
+6089
-556
No files matched your search
+206
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Overview row symmetry regression using the shipped CSS/JS and simulated status.
|
||||
|
||||
Browser DOM geometry only; does not exercise the running RustProxy backend.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from bs4 import BeautifulSoup
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2] / 'src/admin/web'
|
||||
soup = BeautifulSoup((ROOT / 'index.html').read_text(), 'html.parser')
|
||||
widgets = list(soup.select('#dashboard-overview-grid > [data-dashboard-widget]'))
|
||||
assert len(widgets) == 62
|
||||
html = ('<!doctype html><html data-theme="ultra_dark" data-dashboard-density="compact">'
|
||||
'<head><meta charset="utf-8"><style>' + (ROOT / 'style.css').read_text() +
|
||||
'</style></head><body style="margin:0"><div id="test-shell" '
|
||||
'style="padding:8px;box-sizing:border-box"><div class="brand">'
|
||||
'<div class="brand-mark" id="logo-test">R<span>P</span></div></div>'
|
||||
'<section id="panel-overview"><div id="dashboard-overview-grid" '
|
||||
'class="stats-grid dashboard-overview-grid">' +
|
||||
''.join(str(widget) for widget in widgets) + '</div></section></div></body></html>')
|
||||
|
||||
SNAPSHOT = """() => {
|
||||
const grid = document.getElementById('dashboard-overview-grid');
|
||||
const style = getComputedStyle(grid), outer = grid.getBoundingClientRect();
|
||||
const group = window.rustProxyMasonry.groupFor;
|
||||
return {
|
||||
packed: grid.classList.contains('is-packed'),
|
||||
gap: parseFloat(style.columnGap),
|
||||
columns: style.gridTemplateColumns.trim().split(/\\s+/).length,
|
||||
grid: {left:outer.left,right:outer.right,width:outer.width},
|
||||
scroll: grid.scrollWidth, client: grid.clientWidth,
|
||||
cards: [...grid.children].filter(node=>node.dataset.dashboardWidget && !node.hidden).map(node=>{
|
||||
const r=node.getBoundingClientRect();return {id:node.dataset.dashboardWidget,
|
||||
group:group(node.dataset.dashboardWidget),x:r.left,y:r.top,right:r.right,
|
||||
bottom:r.bottom,width:r.width,height:r.height};
|
||||
})
|
||||
};
|
||||
}"""
|
||||
|
||||
|
||||
def validate(data, zoom, width):
|
||||
"""Peers in each row must have one top edge and one bottom edge."""
|
||||
cards = data['cards']; gap = data['gap'] * zoom
|
||||
assert cards and not data['packed'], (width, 'absolute/masonry mode is forbidden')
|
||||
assert data['scroll'] <= data['client'] + 2, (width, 'horizontal overflow')
|
||||
seen_groups = []
|
||||
rows = {}
|
||||
for card in cards:
|
||||
assert card['width'] > 40 and card['height'] > 26, (width, card)
|
||||
assert card['x'] >= data['grid']['left'] - 1, (width, card)
|
||||
assert card['right'] <= data['grid']['right'] + 1, (width, card)
|
||||
if not seen_groups or seen_groups[-1] != card['group']:
|
||||
assert card['group'] not in seen_groups, (width, 'noncontiguous size groups')
|
||||
seen_groups.append(card['group'])
|
||||
row = round(card['y'], 1)
|
||||
rows.setdefault(row, []).append(card)
|
||||
for members in rows.values():
|
||||
assert len({m['group'] for m in members}) == 1, (width, 'mixed sizes in a row', members)
|
||||
assert max(m['bottom'] for m in members) - min(m['bottom'] for m in members) <= 1.5, (width, 'ragged bottom', members)
|
||||
assert max(m['height'] for m in members) - min(m['height'] for m in members) <= 1.5, (width, 'uneven heights', members)
|
||||
for a,b in zip(members,members[1:]):
|
||||
assert abs(b['x']-a['right']-gap) <= 2.0, (width,'wrong horizontal gutter',a,b,gap)
|
||||
line_groups = list(rows.values())
|
||||
for above, below in zip(line_groups,line_groups[1:]):
|
||||
a = above[0]; b=below[0]
|
||||
assert b['y'] >= max(v['bottom'] for v in above) + gap - 2, (width,'vertical gap',a,b)
|
||||
return rows
|
||||
|
||||
with sync_playwright() as playwright:
|
||||
browser = playwright.chromium.launch(headless=True,
|
||||
executable_path=shutil.which('chromium') or shutil.which('chromium-browser'),
|
||||
args=['--no-sandbox'])
|
||||
page = browser.new_page(viewport={'width':1720,'height':950})
|
||||
page.set_content(html, wait_until='domcontentloaded')
|
||||
page.add_script_tag(content=(ROOT / 'app/overview-masonry.js').read_text())
|
||||
page.evaluate("""() => window._overviewOriginal = [...document.querySelectorAll(
|
||||
'#dashboard-overview-grid > [data-dashboard-widget]')].map(n=>[n.dataset.dashboardWidget,n.hidden])""")
|
||||
data=page.evaluate(SNAPSHOT)
|
||||
assert data['columns']==12
|
||||
assert [card['id'] for card in data['cards'][:6]] == [
|
||||
'requests','active','bandwidth','errors','cache','memory']
|
||||
validate(data,1,1720)
|
||||
assert len({round(v['height']) for v in data['cards'][:6]}) == 1
|
||||
page.locator('#dashboard-overview-grid').screenshot(
|
||||
path=str(Path(__file__).parents[3] / 'overview_uniform_default_preview.png'))
|
||||
|
||||
# Screenshot regression: custom ordering previously produced 3 distinctly
|
||||
# sized tiles on the same horizontal band (a metric + two tall charts).
|
||||
ids=['backend_connection_errors','process_diagnostics','network_diagnostics','cache_diagnostics',
|
||||
'connection_chart','traffic_chart','system_chart','backend_table',
|
||||
'requests','active','http_protocol','websocket_protocol',
|
||||
'wide_cpu','wide_memory']
|
||||
page.evaluate("""ids => {
|
||||
const grid=document.getElementById('dashboard-overview-grid');
|
||||
for(const node of grid.children) if(node.dataset.dashboardWidget)
|
||||
node.hidden=!ids.includes(node.dataset.dashboardWidget);
|
||||
grid.prepend(...ids.map(id=>grid.querySelector(`[data-dashboard-widget="${id}"]`)));
|
||||
window.rustProxyMasonry.refresh();
|
||||
}""", ids)
|
||||
changed=page.evaluate(SNAPSHOT)
|
||||
validate(changed,1,1720)
|
||||
assert [card['group'] for card in changed['cards']] == sorted(card['group'] for card in changed['cards'])
|
||||
assert not any(card['id']=='backend_connection_errors' and card['y'] == next(v['y'] for v in changed['cards'] if v['id']=='process_diagnostics') for card in changed['cards'])
|
||||
# 3 uniform diagnostic panels per desktop row, 2 large charts per row.
|
||||
panels=[card for card in changed['cards'] if card['group']==3]
|
||||
assert len({round(card['y']) for card in panels[:3]}) == 1
|
||||
assert len({round(card['height']) for card in panels[:3]}) == 1
|
||||
wides=[card for card in changed['cards'] if card['group']==2]
|
||||
assert len(wides)==2 and abs(wides[0]['height']-wides[1]['height']) < 2
|
||||
page.screenshot(path=str(Path(__file__).parents[3] / 'overview_uniform_preview.png'), full_page=True)
|
||||
|
||||
# Runtime content growth automatically expands all peers in its Grid row,
|
||||
# without ResizeObserver, manual coordinates or animation loops.
|
||||
old_height=panels[0]['height']
|
||||
page.evaluate("""() => {
|
||||
const panel=document.querySelector('[data-dashboard-widget="process_diagnostics"]');
|
||||
const child=document.createElement('div');child.id='expanded';child.style.height='175px';panel.append(child);
|
||||
}""")
|
||||
updated=page.evaluate(SNAPSHOT)
|
||||
validate(updated,1,1720)
|
||||
assert next(card for card in updated['cards'] if card['id']=='process_diagnostics')['height'] > old_height + 80
|
||||
page.evaluate("() => document.getElementById('expanded').remove()")
|
||||
validate(page.evaluate(SNAPSHOT),1,1720)
|
||||
|
||||
radii=page.evaluate("""() => {
|
||||
const node=document.querySelector('[data-dashboard-widget="process_diagnostics"]');
|
||||
const bar=document.createElement('div');bar.className='overview-inline-toolbar';
|
||||
bar.innerHTML='<button class="quiet overview-inline-up">←</button><button class="quiet overview-inline-hide">×</button>';
|
||||
node.append(bar);const out=[];
|
||||
for(const preset of ['default','square','subtle','rounded','extra']) {
|
||||
document.documentElement.dataset.cornerStyle=preset;
|
||||
out.push([getComputedStyle(document.getElementById('logo-test')).borderTopLeftRadius,
|
||||
getComputedStyle(bar.querySelector('.overview-inline-up')).borderTopLeftRadius,
|
||||
getComputedStyle(bar.querySelector('.overview-inline-hide')).borderTopLeftRadius]);
|
||||
}bar.remove();return out;
|
||||
}""")
|
||||
assert len({item[0] for item in radii})==1
|
||||
assert all(item[1]==item[2] for item in radii)
|
||||
|
||||
page.evaluate("""() => {
|
||||
const grid=document.getElementById('dashboard-overview-grid');
|
||||
for(const node of grid.children) if(node.dataset.dashboardWidget) node.hidden=false;
|
||||
window.rustProxyMasonry.refresh();
|
||||
}""")
|
||||
cases=[(1720,0,1,'normal',12),(1350,180,1,'compact',12),
|
||||
(1200,260,1,'normal',6),(900,0,1,'compact',6),
|
||||
(900,190,1,'normal',6),(720,190,1,'ultra',2),
|
||||
(620,0,1,'compact',2),(430,0,1,'ultra',1),
|
||||
(1140,200,.9,'compact',6),(1140,200,1.1,'normal',6),
|
||||
(1140,200,1.3,'normal',6)]
|
||||
for width,sidebar,zoom,density,max_columns in cases:
|
||||
page.set_viewport_size({'width':width,'height':950})
|
||||
page.evaluate("""({sidebar,zoom,density})=>{
|
||||
const shell=document.getElementById('test-shell');
|
||||
shell.style.width=`${(innerWidth-sidebar)/zoom}px`;
|
||||
shell.style.marginLeft=`${sidebar}px`;shell.style.zoom=String(zoom);
|
||||
document.documentElement.dataset.dashboardDensity=density;
|
||||
window.rustProxyMasonry.refresh();
|
||||
}""", {'sidebar':sidebar,'zoom':zoom,'density':density})
|
||||
result=page.evaluate(SNAPSHOT)
|
||||
assert len(result['cards'])==62
|
||||
assert result['columns']<=max_columns, (width, result['columns'], max_columns)
|
||||
validate(result,zoom,width)
|
||||
print(f"PASS: {width}px, zoom={zoom}, groups={len(set(c['group'] for c in result['cards']))}, "
|
||||
f"columns={result['columns']}, gap={result['gap']}px")
|
||||
# Unlock mode adds editing toolbars/padding; rows must remain uniform.
|
||||
page.set_viewport_size({'width':900,'height':950})
|
||||
page.evaluate("""() => {
|
||||
const grid=document.getElementById('dashboard-overview-grid');
|
||||
const shell=document.getElementById('test-shell');
|
||||
shell.style.width='auto';shell.style.zoom='1';shell.style.marginLeft='0';
|
||||
grid.classList.add('is-unlocked');
|
||||
for(const node of grid.children) if(node.dataset.dashboardWidget) {
|
||||
const toolbar=document.createElement('div');toolbar.className='overview-inline-toolbar';
|
||||
toolbar.innerHTML='<button class="quiet overview-inline-grip">⠿</button>'+
|
||||
'<span class="overview-widget-type">Wykres</span>'+
|
||||
'<button class="quiet overview-inline-up">←</button>'+
|
||||
'<button class="quiet overview-inline-down">→</button>'+
|
||||
'<button class="quiet overview-inline-hide">×</button>';
|
||||
node.append(toolbar);
|
||||
}
|
||||
}""")
|
||||
validate(page.evaluate(SNAPSHOT),1,900)
|
||||
page.evaluate("""() => {
|
||||
const grid=document.getElementById('dashboard-overview-grid');
|
||||
grid.classList.remove('is-unlocked');
|
||||
grid.querySelectorAll('.overview-inline-toolbar').forEach(toolbar=>toolbar.remove());
|
||||
}""")
|
||||
|
||||
page.set_viewport_size({'width':1720,'height':950})
|
||||
page.evaluate("""() => {
|
||||
const grid=document.getElementById('dashboard-overview-grid'),shell=document.getElementById('test-shell');
|
||||
shell.style.width='auto';shell.style.marginLeft='0';shell.style.zoom='1';
|
||||
document.documentElement.dataset.dashboardDensity='compact';
|
||||
grid.append(...window._overviewOriginal.map(([id,hidden])=>{
|
||||
const node=grid.querySelector(`[data-dashboard-widget="${id}"]`);
|
||||
node.hidden=hidden;return node;
|
||||
}));window.rustProxyMasonry.refresh();
|
||||
}""")
|
||||
final=page.evaluate(SNAPSHOT)
|
||||
validate(final,1,1720)
|
||||
assert [node['id'] for node in final['cards'][:6]]==['requests','active','bandwidth','errors','cache','memory']
|
||||
browser.close()
|
||||
print('PASS: 62 cards in aligned rows; no floating heights, safe resize and zoom')
|
||||
Reference in new issue
Block a user