v0.32.6_official
This commit is contained in:
1 parent
fc8711d4d8
commit
d2c6cf59a9
16 files changed
+1801
-1027
No files matched your search
@@ -65,6 +65,18 @@ with sync_playwright() as playwright:
|
||||
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')
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Optional real-CSS/JS Chromium regression for the horizontal Overview card ribbon.
|
||||
|
||||
Mocks authenticated preferences; no server or physical iOS device involved.
|
||||
Run with: python tools/checks/dashboard_appearance_ribbon_0326_browser.py
|
||||
"""
|
||||
from pathlib import Path
|
||||
from playwright.sync_api import sync_playwright
|
||||
import shutil
|
||||
import json
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
html=(root/'src/admin/web/index.html').read_text()
|
||||
css=(root/'src/admin/web/style.css').read_text()
|
||||
prefs=(root/'src/admin/web/app/preferences.js').read_text()
|
||||
registry=json.loads((root/'src/admin/web/dashboard-widgets.json').read_text())
|
||||
translations=json.loads((root/'langs/pl_pl.json').read_text())['translations']
|
||||
heading=html.index('data-i18n="dashboardLayout"')
|
||||
section_start=html.rfind('<section class="appearance-section">',0,heading)
|
||||
section_end=html.index('</section>',heading)+len('</section>')
|
||||
assert section_start >= 0
|
||||
source=html[section_start:section_end]
|
||||
start=prefs.index('function renderDashboardWidgetSettings() {')
|
||||
end=prefs.index('function refreshChartStylePalette(',start)
|
||||
code=prefs[start:end]
|
||||
stub='''
|
||||
const $ = id => document.getElementById(id);
|
||||
const el = (tag, txt, classes) => { let node=document.createElement(tag); if (txt!==undefined) node.textContent=String(txt); if (classes) node.className=classes; return node; };
|
||||
const labels = __TRANSLATIONS__;
|
||||
const registry = __REGISTRY__;
|
||||
const ADMIN_DASHBOARD_BY_ID = new Map(registry.map(def=>[def.id,def]));
|
||||
const order = registry.map(def=>def.id);
|
||||
let adminPreferences = {dashboard_widgets:order, dashboard_hidden_widgets:registry.filter(def=>def.default_hidden).map(def=>def.id)};
|
||||
const ADMIN_APPEARANCE_DEFAULTS = adminPreferences;
|
||||
function t(key){ return labels[key] || key; }
|
||||
function dashboardWidgetLabel(id) { return t(ADMIN_DASHBOARD_BY_ID.get(id)?.label_key || id); }
|
||||
function normalizeDashboardWidgets(value){return Array.isArray(value)?value:order;}
|
||||
function normalizeHiddenDashboardWidgets(value){return Array.isArray(value)?value:[];}
|
||||
function applyDashboardLayout(order,hidden){ window.__state={order,hidden}; }
|
||||
async function saveAppearancePreference(next){ Object.assign(adminPreferences,next);return {}; }
|
||||
'''.replace('__TRANSLATIONS__',json.dumps(translations,ensure_ascii=False)).replace('__REGISTRY__',json.dumps(registry))
|
||||
with sync_playwright() as p:
|
||||
browser=p.chromium.launch(executable_path=shutil.which('chromium') or shutil.which('chromium-browser'),args=['--no-sandbox'],headless=True)
|
||||
for width,height in [(1500,950),(393,850)]:
|
||||
page=browser.new_page(viewport={'width':width,'height':height},device_scale_factor=1)
|
||||
page.set_content('<!doctype html><html data-theme="dark" lang="pl"><head><style>'+css+'</style></head><body><main style="margin:0; padding:28px 16px; width:100%; max-width:100%; box-sizing:border-box">'+source+'</main></body></html>',wait_until='domcontentloaded')
|
||||
page.add_script_tag(content=stub+code+'\nrenderDashboardWidgetSettings();')
|
||||
page.wait_for_timeout(90)
|
||||
metrics=page.evaluate('''() => {
|
||||
const list=document.getElementById('dashboard-widget-list'), section=document.querySelector('.appearance-section');
|
||||
const parent=list.closest('.dashboard-widget-settings');
|
||||
return { total: list.children.length, listHeight:list.getBoundingClientRect().height,
|
||||
listWidth:list.getBoundingClientRect().width, contentWidth:list.scrollWidth,
|
||||
sectionHeight:section.getBoundingClientRect().height,
|
||||
overflowX:getComputedStyle(list).overflowX,
|
||||
firstWidth:list.firstElementChild.getBoundingClientRect().width,
|
||||
density: document.querySelector('.dashboard-density-setting').getBoundingClientRect().width,
|
||||
widget:parent.getBoundingClientRect().width,
|
||||
viewportOverflow:document.documentElement.scrollWidth>window.innerWidth,
|
||||
nextDisabled:document.getElementById('dashboard-widget-scroll-next').disabled};
|
||||
}''')
|
||||
print(width,'COMPACT',metrics)
|
||||
assert metrics['total']==len(registry)
|
||||
assert metrics['listHeight']<66,(width,metrics)
|
||||
assert metrics['contentWidth']>metrics['listWidth'],(width,metrics)
|
||||
assert metrics['sectionHeight'] < (320 if width>700 else 450), (width,metrics)
|
||||
assert not metrics['viewportOverflow']
|
||||
page.locator('#dashboard-widget-scroll-next').click()
|
||||
page.wait_for_timeout(450)
|
||||
pos=page.evaluate('document.getElementById("dashboard-widget-list").scrollLeft')
|
||||
print(width,'scroll after next:',pos)
|
||||
assert pos>10
|
||||
page.locator('.dashboard-widget-setting').nth(7).locator('button.dashboard-widget-toggle').click(force=True)
|
||||
page.wait_for_timeout(60)
|
||||
state=page.evaluate('window.__state')
|
||||
assert state and len(state['order'])==len(registry)
|
||||
retained=page.evaluate('document.getElementById("dashboard-widget-list").scrollLeft')
|
||||
assert retained>=pos-15,(pos,retained)
|
||||
page.locator('#dashboard-widget-expand').click()
|
||||
page.wait_for_timeout(60)
|
||||
expanded=page.evaluate('''() => { const list = document.getElementById("dashboard-widget-list"); return { height:list.getBoundingClientRect().height, mode:getComputedStyle(list).display, count:list.children.length, expanded:document.getElementById('dashboard-widget-expand').getAttribute('aria-expanded'), prevHidden:document.getElementById('dashboard-widget-scroll-prev').hidden } }''')
|
||||
print(width,'EXPANDED',expanded)
|
||||
assert expanded['mode']=='grid'
|
||||
assert expanded['expanded']=='true' and expanded['prevHidden']
|
||||
assert expanded['height']>metrics['listHeight']*3
|
||||
page.locator('#dashboard-widget-expand').click()
|
||||
assert page.locator('#dashboard-widget-expand').get_attribute('aria-expanded')=='false'
|
||||
outfile=str(root.parent / f'dashboard_appearance_0326_preview_{width}.png')
|
||||
page.locator('.appearance-section').screenshot(path=outfile)
|
||||
print('screenshot',outfile)
|
||||
page.close()
|
||||
browser.close()
|
||||
@@ -0,0 +1,36 @@
|
||||
// Appearance ribbon: compact by default, with full-width expansion and the same
|
||||
// persisted visibility/reorder controls. No browser or Rust service required.
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const html = fs.readFileSync('src/admin/web/index.html', 'utf8');
|
||||
const css = fs.readFileSync('src/admin/web/style.css', 'utf8');
|
||||
const js = fs.readFileSync('src/admin/web/app/preferences.js', 'utf8');
|
||||
const registry = JSON.parse(fs.readFileSync('src/admin/web/dashboard-widgets.json', 'utf8'));
|
||||
const locales = ['en_US', 'pl_pl'].map(id => JSON.parse(fs.readFileSync(`langs/${id}.json`, 'utf8')).translations);
|
||||
for (const id of ['dashboard-widget-list', 'dashboard-widget-expand', 'dashboard-widget-scroll-prev',
|
||||
'dashboard-widget-scroll-next', 'overview-layout-open-settings', 'ui-dashboard-density']) {
|
||||
assert(html.includes(`id="${id}"`), `missing ${id}`);
|
||||
}
|
||||
assert.match(html, /class="appearance-settings-grid dashboard-appearance-grid"/);
|
||||
assert.match(css, /\.appearance-settings-grid\.dashboard-appearance-grid\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\)/s,
|
||||
'dashboard setting layout must use the full available width');
|
||||
assert.match(css, /\.dashboard-appearance-grid \.dashboard-widget-list\s*\{[^}]*flex-wrap:\s*nowrap;[^}]*overflow-x:\s*auto;/s,
|
||||
'the default card selector must be a horizontal ribbon, not a tall two-column list');
|
||||
assert.match(css, /\.dashboard-appearance-grid \.dashboard-widget-list\.is-expanded\s*\{[^}]*display:\s*grid;/s);
|
||||
assert.match(css, /\.dashboard-widget-settings-controls > \.dashboard-widget-scroll-button/);
|
||||
assert(js.includes('list.scrollLeft = previousScroll'), 'rebuilding settings must preserve horizontal scroll position');
|
||||
assert(js.includes('focusedId') && js.includes('preventScroll: true'), 'keyboard focus must survive updates');
|
||||
assert(js.includes('saveAppearancePreference({ dashboard_hidden_widgets:') &&
|
||||
js.includes('saveAppearancePreference({ dashboard_widgets:'), 'both APIs must retain persistence');
|
||||
assert(js.includes('const up = el("button", "←"') && js.includes('const down = el("button", "→"'),
|
||||
'horizontal order controls must align with the ribbon');
|
||||
assert.equal(registry.length, 62, 'all configured Overview cards must remain available');
|
||||
for (const translation of locales) {
|
||||
for (const key of ['dashboardWidgetsShowAll', 'dashboardWidgetsCompact', 'dashboardWidgetsScrollBack',
|
||||
'dashboardWidgetsScrollNext', 'dashboardWidgetsScrollHelp',
|
||||
'dashboardWidgetsMoveEarlier', 'dashboardWidgetsMoveLater']) {
|
||||
assert(translation[key], `missing ${key}`);
|
||||
}
|
||||
}
|
||||
console.log('PASS: full-width compact Appearance ribbon with expandable 62-card layout, keyboard focus and preference persistence');
|
||||
@@ -100,8 +100,10 @@ assert(ids.every(id => cards.get(id).hidden), 'hiding cards must still work');
|
||||
assert(!cards.get('requests').hidden, 'legacy widgets must retain their visibility');
|
||||
// Native title tooltips do not participate in layout or create horizontal scroll.
|
||||
assert.doesNotMatch(css, /dashboard-widget-(?:description|setting-label-block)/, 'obsolete custom tooltip styles must be removed');
|
||||
assert.match(css, /grid-template-columns: repeat\(auto-fit, minmax\(min\(260px, 100%\), 1fr\)\)/,
|
||||
'effective Appearance card columns must preserve a readable name and right-aligned arrows');
|
||||
assert.match(css, /\.dashboard-appearance-grid \.dashboard-widget-list\s*\{[^}]*overflow-x: auto;/s,
|
||||
'Appearance must use a compact horizontal ribbon instead of long half-width columns');
|
||||
assert.match(css, /\.dashboard-appearance-grid \.dashboard-widget-list > \.dashboard-widget-setting\s*\{[^}]*min-width: 226px;/s,
|
||||
'Appearance ribbon must retain readable labels and right-aligned reorder buttons');
|
||||
assert.doesNotMatch(prefsSource, /role", "tooltip"|aria-describedby|dashboard-widget-description/,
|
||||
'old popover DOM and associations must be removed');
|
||||
const widgetList = { children: [], replaceChildren(...children) { this.children = children; } };
|
||||
|
||||
Reference in new issue
Block a user