diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-20 16:43:53 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-20 16:43:53 -0400 |
| commit | 5627f137510ce1cf12a08abeed8c89abf3a83f5e (patch) | |
| tree | 4e3fad60a43b8725f5f495862ced0580717c1498 /scripts/theme-studio | |
| parent | 6df4ebdcd7cc8102f00910c771a14e0c772061d5 (diff) | |
| download | dotemacs-5627f137510ce1cf12a08abeed8c89abf3a83f5e.tar.gz dotemacs-5627f137510ce1cf12a08abeed8c89abf3a83f5e.zip | |
refactor(theme-studio): extract assertPreviewFaces for the 3 preview-face gates
The #mdtest, #mupreviewtest, and #gnustest browser gates each copy-pasted the
same preview-face validation: render the preview, assert it exercises enough
data-faces, that every data-face is real for the package, and that the required
faces are present. Lift that into one assertPreviewFaces(A, html, faces, min,
name, required) helper. Each gate keeps its literal location.hash==='#NAMEtest'
check (run-tests.sh greps that to discover gates) and its own title/result-div.
Verified: all 44 gates green, and a deliberately-broken required face still makes
its gate report FAIL (the helper can't manufacture greens).
Diffstat (limited to 'scripts/theme-studio')
| -rw-r--r-- | scripts/theme-studio/browser-gates.js | 44 | ||||
| -rw-r--r-- | scripts/theme-studio/theme-studio.html | 44 |
2 files changed, 40 insertions, 48 deletions
diff --git a/scripts/theme-studio/browser-gates.js b/scripts/theme-studio/browser-gates.js index 5d747b1d8..f4556ba5b 100644 --- a/scripts/theme-studio/browser-gates.js +++ b/scripts/theme-studio/browser-gates.js @@ -1,3 +1,17 @@ +// Shared preview-face validator for the #mdtest / #mupreviewtest / #gnustest +// gates: render HTML into a detached div, then assert it exercises at least +// MINCOUNT data-faces, that every data-face is a real face of the package +// (drawn from FACES, the app's face rows), and that each face in REQUIRED is +// present. A is the gate's assertion collector; NAME labels the failure note. +function assertPreviewFaces(A, html, faces, minCount, name, required){ + const box=document.createElement('div');box.innerHTML=html; + const valid=new Set((faces||[]).map(r=>r[0])); + const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); + A(used.length>=minCount,'preview exercises many faces ('+used.length+')'); + const bad=used.filter(f=>!valid.has(f)); + A(bad.length===0,'every data-face is a real '+name+' face; bad='+bad.join(',')); + for(const f of required) A(used.includes(f),'preview includes '+f); +} // Phase-1 self-test (open with #selftest): seed -> export -> import -> compare. function pkgSelftest(){ const seeded=seedPkgmap(); @@ -754,28 +768,16 @@ if(location.hash==='#mdtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ A(APPS['markdown-mode']&&APPS['markdown-mode'].preview==='markdown','markdown-mode wired to the markdown preview'); A(!!PACKAGE_PREVIEWS['markdown'],'markdown renderer registered'); if(PACKAGE_PREVIEWS['markdown']&&APPS['markdown-mode']){ - const box=document.createElement('div');box.innerHTML=PACKAGE_PREVIEWS['markdown'](); - const valid=new Set(APPS['markdown-mode'].faces.map(r=>r[0])); - const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); - A(used.length>=15,'preview exercises many faces ('+used.length+')'); - const bad=used.filter(f=>!valid.has(f)); - A(bad.length===0,'every data-face is a real markdown face; bad='+bad.join(',')); - for(const f of ['markdown-header-face-1','markdown-bold-face','markdown-inline-code-face','markdown-blockquote-face','markdown-gfm-checkbox-face','markdown-table-face']) - A(used.includes(f),'preview includes '+f); + assertPreviewFaces(A, PACKAGE_PREVIEWS['markdown'](), APPS['markdown-mode'].faces, 15, 'markdown', + ['markdown-header-face-1','markdown-bold-face','markdown-inline-code-face','markdown-blockquote-face','markdown-gfm-checkbox-face','markdown-table-face']); } document.title='MDTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='mdtest';d.textContent='MDTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);} // mu4e-preview gate (open with #mupreviewtest): the mu4e preview is a realistic // headers list + message view, and every data-face it emits is a real mu4e face. if(location.hash==='#mupreviewtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; - const box=document.createElement('div');box.innerHTML=renderMu4ePreview(); - const valid=new Set((APPS['mu4e']&&APPS['mu4e'].faces||[]).map(r=>r[0])); - const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); - A(used.length>=20,'preview exercises many faces ('+used.length+')'); - const bad=used.filter(f=>!valid.has(f)); - A(bad.length===0,'every data-face is a real mu4e face; bad='+bad.join(',')); - for(const f of ['mu4e-unread-face','mu4e-flagged-face','mu4e-replied-face','mu4e-draft-face','mu4e-trashed-face','mu4e-header-highlight-face','mu4e-header-marks-face','mu4e-contact-face','mu4e-compose-separator-face']) - A(used.includes(f),'preview includes '+f); + assertPreviewFaces(A, renderMu4ePreview(), APPS['mu4e']&&APPS['mu4e'].faces, 20, 'mu4e', + ['mu4e-unread-face','mu4e-flagged-face','mu4e-replied-face','mu4e-draft-face','mu4e-trashed-face','mu4e-header-highlight-face','mu4e-header-marks-face','mu4e-contact-face','mu4e-compose-separator-face']); document.title='MUPREVIEWTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='mupreviewtest';d.textContent='MUPREVIEWTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);} // gnus-preview gate (open with #gnustest): gnus is its own view package (it drives @@ -783,14 +785,8 @@ if(location.hash==='#mupreviewtest'){let ok=true;const notes=[];const A=(c,n)=>{ if(location.hash==='#gnustest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; A(!!APPS['gnus'],'gnus is a registered view package'); A(APPS['gnus']&&APPS['gnus'].preview==='gnus','gnus uses the gnus preview renderer'); - const box=document.createElement('div');box.innerHTML=renderGnusPreview(); - const valid=new Set((APPS['gnus']&&APPS['gnus'].faces||[]).map(r=>r[0])); - const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); - A(used.length>=20,'preview exercises many faces ('+used.length+')'); - const bad=used.filter(f=>!valid.has(f)); - A(bad.length===0,'every data-face is a real gnus face; bad='+bad.join(',')); - for(const f of ['gnus-header-name','gnus-header-from','gnus-header-subject','gnus-cite-1','gnus-cite-attribution','gnus-signature','gnus-button','gnus-emphasis-highlight-words']) - A(used.includes(f),'preview includes '+f); + assertPreviewFaces(A, renderGnusPreview(), APPS['gnus']&&APPS['gnus'].faces, 20, 'gnus', + ['gnus-header-name','gnus-header-from','gnus-header-subject','gnus-cite-1','gnus-cite-attribution','gnus-signature','gnus-button','gnus-emphasis-highlight-words']); document.title='GNUSTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='gnustest';d.textContent='GNUSTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);} // picker-distinct gate (open with #pickertest): the color picker panel must stand diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index dada0d2c9..76cc80a51 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -3053,6 +3053,20 @@ function initApp(){ } initApp(); addEventListener('resize',()=>{syncMockHeight();syncPkgHeight();}); +// Shared preview-face validator for the #mdtest / #mupreviewtest / #gnustest +// gates: render HTML into a detached div, then assert it exercises at least +// MINCOUNT data-faces, that every data-face is a real face of the package +// (drawn from FACES, the app's face rows), and that each face in REQUIRED is +// present. A is the gate's assertion collector; NAME labels the failure note. +function assertPreviewFaces(A, html, faces, minCount, name, required){ + const box=document.createElement('div');box.innerHTML=html; + const valid=new Set((faces||[]).map(r=>r[0])); + const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); + A(used.length>=minCount,'preview exercises many faces ('+used.length+')'); + const bad=used.filter(f=>!valid.has(f)); + A(bad.length===0,'every data-face is a real '+name+' face; bad='+bad.join(',')); + for(const f of required) A(used.includes(f),'preview includes '+f); +} // Phase-1 self-test (open with #selftest): seed -> export -> import -> compare. function pkgSelftest(){ const seeded=seedPkgmap(); @@ -3809,28 +3823,16 @@ if(location.hash==='#mdtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ A(APPS['markdown-mode']&&APPS['markdown-mode'].preview==='markdown','markdown-mode wired to the markdown preview'); A(!!PACKAGE_PREVIEWS['markdown'],'markdown renderer registered'); if(PACKAGE_PREVIEWS['markdown']&&APPS['markdown-mode']){ - const box=document.createElement('div');box.innerHTML=PACKAGE_PREVIEWS['markdown'](); - const valid=new Set(APPS['markdown-mode'].faces.map(r=>r[0])); - const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); - A(used.length>=15,'preview exercises many faces ('+used.length+')'); - const bad=used.filter(f=>!valid.has(f)); - A(bad.length===0,'every data-face is a real markdown face; bad='+bad.join(',')); - for(const f of ['markdown-header-face-1','markdown-bold-face','markdown-inline-code-face','markdown-blockquote-face','markdown-gfm-checkbox-face','markdown-table-face']) - A(used.includes(f),'preview includes '+f); + assertPreviewFaces(A, PACKAGE_PREVIEWS['markdown'](), APPS['markdown-mode'].faces, 15, 'markdown', + ['markdown-header-face-1','markdown-bold-face','markdown-inline-code-face','markdown-blockquote-face','markdown-gfm-checkbox-face','markdown-table-face']); } document.title='MDTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='mdtest';d.textContent='MDTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);} // mu4e-preview gate (open with #mupreviewtest): the mu4e preview is a realistic // headers list + message view, and every data-face it emits is a real mu4e face. if(location.hash==='#mupreviewtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; - const box=document.createElement('div');box.innerHTML=renderMu4ePreview(); - const valid=new Set((APPS['mu4e']&&APPS['mu4e'].faces||[]).map(r=>r[0])); - const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); - A(used.length>=20,'preview exercises many faces ('+used.length+')'); - const bad=used.filter(f=>!valid.has(f)); - A(bad.length===0,'every data-face is a real mu4e face; bad='+bad.join(',')); - for(const f of ['mu4e-unread-face','mu4e-flagged-face','mu4e-replied-face','mu4e-draft-face','mu4e-trashed-face','mu4e-header-highlight-face','mu4e-header-marks-face','mu4e-contact-face','mu4e-compose-separator-face']) - A(used.includes(f),'preview includes '+f); + assertPreviewFaces(A, renderMu4ePreview(), APPS['mu4e']&&APPS['mu4e'].faces, 20, 'mu4e', + ['mu4e-unread-face','mu4e-flagged-face','mu4e-replied-face','mu4e-draft-face','mu4e-trashed-face','mu4e-header-highlight-face','mu4e-header-marks-face','mu4e-contact-face','mu4e-compose-separator-face']); document.title='MUPREVIEWTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='mupreviewtest';d.textContent='MUPREVIEWTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);} // gnus-preview gate (open with #gnustest): gnus is its own view package (it drives @@ -3838,14 +3840,8 @@ if(location.hash==='#mupreviewtest'){let ok=true;const notes=[];const A=(c,n)=>{ if(location.hash==='#gnustest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; A(!!APPS['gnus'],'gnus is a registered view package'); A(APPS['gnus']&&APPS['gnus'].preview==='gnus','gnus uses the gnus preview renderer'); - const box=document.createElement('div');box.innerHTML=renderGnusPreview(); - const valid=new Set((APPS['gnus']&&APPS['gnus'].faces||[]).map(r=>r[0])); - const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); - A(used.length>=20,'preview exercises many faces ('+used.length+')'); - const bad=used.filter(f=>!valid.has(f)); - A(bad.length===0,'every data-face is a real gnus face; bad='+bad.join(',')); - for(const f of ['gnus-header-name','gnus-header-from','gnus-header-subject','gnus-cite-1','gnus-cite-attribution','gnus-signature','gnus-button','gnus-emphasis-highlight-words']) - A(used.includes(f),'preview includes '+f); + assertPreviewFaces(A, renderGnusPreview(), APPS['gnus']&&APPS['gnus'].faces, 20, 'gnus', + ['gnus-header-name','gnus-header-from','gnus-header-subject','gnus-cite-1','gnus-cite-attribution','gnus-signature','gnus-button','gnus-emphasis-highlight-words']); document.title='GNUSTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='gnustest';d.textContent='GNUSTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);} // picker-distinct gate (open with #pickertest): the color picker panel must stand |
