feat: publish researched offline HTML presentation skill
This commit is contained in:
commit
50eab114ae
35 files changed
+1201
No files matched your search
@@ -0,0 +1,30 @@
|
||||
"""Build an offline, single-file deck with no package install or network."""
|
||||
from pathlib import Path
|
||||
import base64, hashlib, json, re
|
||||
R=Path(__file__).resolve().parents[1]
|
||||
def build():
|
||||
vendor=R/'assets/vendor'; s=(R/'assets/starter.html').read_text()
|
||||
manifest=R/'vendor-manifest.json'
|
||||
if manifest.exists():
|
||||
for entry in json.loads(manifest.read_text()):
|
||||
assert hashlib.sha256((R/entry['path']).read_bytes()).hexdigest()==entry['sha256'], entry['path']
|
||||
nodes=[]
|
||||
labels=['People','Process','Platform','Quality','Cost','Risk']
|
||||
details=['People: who owns the decision?','Process: how does work move?','Platform: what enables delivery?','Quality: what evidence proves fitness?','Cost: what constraint shapes the choice?','Risk: what could invalidate the plan?']
|
||||
for i,(label,detail) in enumerate(zip(labels,details)):
|
||||
x=[250,550,850][i%3];y=65 if i<3 else 305
|
||||
nodes.append(f'<g role="button" tabindex="0" aria-label="{label}" aria-pressed="false" data-node="{i}" data-detail="{detail}"><rect x="{x-110}" y="{y-32}" width="220" height="64"/><text x="{x}" y="{y}">{label}</text></g>')
|
||||
s=s.replace('<!-- NODES -->','\n'.join(nodes))
|
||||
for marker,file in [('REVEAL_CSS','reveal.css'),('REVEAL_JS','reveal.js'),('ANIME_JS','anime.umd.min.js')]:
|
||||
text=(vendor/file).read_text();text=re.sub(r'//# sourceMappingURL=.*','',text)
|
||||
s=s.replace('/* '+marker+' */',text)
|
||||
fonts=''
|
||||
for file,weight in [('work-sans.woff2',400),('work-sans-bold.woff2',700)]:
|
||||
fonts+=f"@font-face{{font-family:'Work Sans';font-weight:{weight};font-style:normal;font-display:block;src:url(data:font/woff2;base64,{base64.b64encode((vendor/file).read_bytes()).decode()}) format('woff2');}}"
|
||||
s=s.replace('/* FONT_CSS */',fonts).replace('/* APP_JS */',(R/'assets/app.js').read_text())
|
||||
licenses='\n'.join((vendor/f).read_text() for f in ['reveal-LICENSE','anime-LICENSE.md','work-sans-LICENSE'])
|
||||
s=s.replace('<!-- LICENSES -->','<!-- THIRD-PARTY NOTICES\n'+licenses.replace('--','—')+'\n-->')
|
||||
out=R/'dist';out.mkdir(exist_ok=True);p=out/'presentation-html-demo-0.1.0.html';p.write_text(s)
|
||||
report={'file':p.name,'sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'bytes':p.stat().st_size,'slides':6,'interactive_nodes':6,'reveal':'6.0.2','anime':'4.5.0'}
|
||||
(out/'manifest.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report))
|
||||
if __name__=='__main__':build()
|
||||
@@ -0,0 +1,37 @@
|
||||
import { chromium } from '@playwright/test';
|
||||
import fs from 'node:fs';import path from 'node:path';import {pathToFileURL} from 'node:url';import assert from 'node:assert/strict';import crypto from 'node:crypto';
|
||||
const root=path.resolve(import.meta.dirname,'..');const file=process.env.DECK_PATH||path.join(root,'dist/presentation-html-demo-0.1.0.html');
|
||||
const executablePath=process.env.CHROMIUM_EXECUTABLE;
|
||||
const browser=await chromium.launch({headless:true,...(executablePath?{executablePath}:{})});
|
||||
const report={file,sha256:crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'),runs:[]};
|
||||
fs.mkdirSync(path.join(root,'evidence'),{recursive:true});
|
||||
try {
|
||||
for(const reducedMotion of ['no-preference','reduce']){
|
||||
const context=await browser.newContext({viewport:{width:1440,height:900},reducedMotion,offline:true});const page=await context.newPage();const errors=[],requests=[];
|
||||
page.on('pageerror',e=>errors.push(String(e)));page.on('console',m=>{if(m.type()==='error')errors.push(m.text());});page.on('request',r=>{if(/^https?:/.test(r.url()))requests.push(r.url());});
|
||||
await page.goto(pathToFileURL(file).href);await page.waitForFunction(()=>window.deckReady);await page.evaluate(()=>document.fonts.ready);
|
||||
assert.equal(await page.evaluate(()=>document.fonts.check('28px "Work Sans"')),true);
|
||||
assert.equal(await page.evaluate(()=>getComputedStyle(document.body).backgroundColor),'rgb(16, 30, 37)');
|
||||
assert.equal(await page.evaluate(()=>getComputedStyle(document.querySelector('.reveal')).color),'rgb(244, 243, 237)');
|
||||
const slides=await page.locator('.slides>section').count();assert.equal(slides,6);
|
||||
let testedNodes=0;
|
||||
for(let i=0;i<slides;i++){
|
||||
await page.evaluate(i=>Reveal.slide(i),i);await page.waitForTimeout(900);
|
||||
const clipped=await page.evaluate(()=>{const s=Reveal.getCurrentSlide(),r=s.getBoundingClientRect();return [...s.querySelectorAll('h1,h2,p,strong,svg text,button')].filter(e=>{const b=e.getBoundingClientRect();return b.left<r.left-2||b.right>r.right+2||b.top<r.top-2||b.bottom>r.bottom+2;}).map(e=>e.textContent);});assert.deepEqual(clipped,[],'Clipping slide '+i);
|
||||
const nodes=page.locator('.present [data-node]');
|
||||
for(let j=0;j<await nodes.count();j++){
|
||||
const n=nodes.nth(j);await n.focus();await page.keyboard.press('Enter');assert.equal(await n.getAttribute('aria-pressed'),'true');assert.equal(await page.locator('#detail').textContent(),await n.getAttribute('data-detail'));await page.keyboard.press('Space');assert.equal(await n.getAttribute('aria-pressed'),'false');await n.click();assert.equal(await n.getAttribute('aria-pressed'),'true');await n.click();testedNodes++;
|
||||
}
|
||||
if(reducedMotion==='no-preference')await page.screenshot({path:path.join(root,`evidence/slide-${String(i+1).padStart(2,'0')}.png`)});
|
||||
}
|
||||
assert.equal(testedNodes,6);await page.evaluate(()=>Reveal.slide(0));await page.keyboard.press('ArrowRight');assert.equal(await page.evaluate(()=>Reveal.getIndices().h),1);await page.keyboard.press('ArrowLeft');assert.equal(await page.evaluate(()=>Reveal.getIndices().h),0);
|
||||
await page.keyboard.press('Escape');assert.equal(await page.evaluate(()=>Reveal.isOverview()),true);await page.keyboard.press('Escape');
|
||||
await page.locator('#fullscreen').click();await page.waitForFunction(()=>!!document.fullscreenElement);assert.equal(await page.locator('#fullscreen').getAttribute('aria-pressed'),'true');await page.keyboard.press('f');await page.waitForFunction(()=>!document.fullscreenElement);
|
||||
await page.keyboard.press('f');await page.waitForFunction(()=>!!document.fullscreenElement);await page.locator('#fullscreen').click();await page.waitForFunction(()=>!document.fullscreenElement);
|
||||
await page.evaluate(()=>Reveal.slide(3));await page.waitForFunction(()=>location.hash.includes('sequence'));await page.reload();await page.waitForFunction(()=>window.deckReady);assert.equal(await page.evaluate(()=>Reveal.getIndices().h),3);
|
||||
if(reducedMotion==='reduce'){assert.equal(await page.evaluate(()=>presentationQA.active()),0);assert.equal(await page.locator('#motion').getAttribute('aria-pressed'),'false');}
|
||||
await page.emulateMedia({media:'print'});await page.evaluate(()=>dispatchEvent(new Event('beforeprint')));assert.equal(await page.evaluate(()=>getComputedStyle(document.querySelector('.toolbar')).display),'none');
|
||||
assert.deepEqual(errors,[]);assert.deepEqual(requests,[]);report.runs.push({reducedMotion,slides,testedNodes,errors,networkRequests:requests,clipping:0,keyboard:true,fullscreen:true,overview:true,reload:true,print:true});await context.close();
|
||||
}
|
||||
fs.writeFileSync(path.join(root,'evidence/test-report.json'),JSON.stringify(report,null,2)+'\n');console.log(JSON.stringify(report,null,2));
|
||||
} finally {await browser.close();}
|
||||
Reference in new issue
Block a user