| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 |
- import fs from 'node:fs/promises';
- import path from 'node:path';
- import assert from 'node:assert/strict';
- import { pathToFileURL } from 'node:url';
- const out=path.resolve('docs/workshop-report-layout-20261111');
- const raw=JSON.parse(await fs.readFile('docs/workshop-argument-quality-20261111/example-fixture.json','utf8'));
- const original=JSON.stringify(raw);
- const origin='http://127.0.0.1:4181';
- const {chromium}=await import(pathToFileURL(path.resolve('work/workshop-manual-tools/node_modules/playwright/index.mjs')).href);
- const browser=await chromium.launch({headless:true,executablePath:'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'});
- const checks=[],errors=[],requests=[],layouts=[];
- try {
- const context=await browser.newContext({viewport:{width:1440,height:1050}});
- await context.route('**/*',route=>{
- const request=route.request(),url=new URL(request.url());
- if(url.pathname==='/api/functions'||url.pathname==='/__dev/member-groups') {
- const body=request.postDataJSON();requests.push(body?.path);
- const data=body?.path==='/ksfGetPublicBoard'?raw:body?.path==='/ksfGetPublicMemberGroups'?{schema:'ksf.workshop-member-groups.v1',groups:[],members:[]}:null;
- if(!data){errors.push('Unexpected API '+body?.path);return route.abort();}
- return route.fulfill({contentType:'application/json',body:JSON.stringify({result:{ok:true,data}})});
- }
- if(url.origin===origin&&request.method()==='GET')return route.continue();
- errors.push('Unexpected request '+url.origin+url.pathname);return route.abort();
- });
- const page=await context.newPage();page.on('pageerror',error=>errors.push(error.message));
- // Before captures use the exact pre-edit report component and the same read-only fixture.
- await page.route('**/src/pages/view-report.js',route=>route.fulfill({contentType:'text/javascript',path:path.join(out,'before/view-report.js')}));
- await page.goto(origin+'/report/argument-preview?mode=sample');
- await page.locator('[data-evidence-reading]').waitFor();
- await page.screenshot({path:path.join(out,'before-overview-1440.png')});
- await page.locator('[data-evidence-reading]').scrollIntoViewIfNeeded();
- await page.screenshot({path:path.join(out,'before-evidence-1440.png')});
- await page.unroute('**/src/pages/view-report.js');
- for(const width of [1440,1280,768,390,320]) {
- await page.setViewportSize({width,height:1050});
- await page.goto(origin+'/report/argument-preview?mode=sample');
- await page.locator('[data-evidence-reading]').waitFor();
- await page.evaluate(()=>document.fonts.ready);
- const report=page.locator('view-report');
- const measurements=await report.evaluate(host=>{
- const root=host.shadowRoot;
- const rect=el=>{const r=el.getBoundingClientRect();return {x:Math.round(r.x),y:Math.round(r.y),width:Math.round(r.width),height:Math.round(r.height)};};
- const groups=[...root.querySelectorAll('.evidence-catalog>.evidence-group')].map(rect);
- const cards=[...root.querySelectorAll('.evidence-catalog .evidence-excerpt')].map(el=>({...rect(el),scrollHeight:el.scrollHeight,clientHeight:el.clientHeight}));
- return {width:innerWidth,scrollWidth:document.documentElement.scrollWidth,groups,cards,
- emptyHeadings:[...root.querySelectorAll('h4')].filter(el=>!el.textContent.trim()).length,
- dividers:[...root.querySelectorAll('.evidence-excerpt')].every(el=>getComputedStyle(el.querySelector('.evidence-caption')).borderBottomWidth==='1px'&&getComputedStyle(el.querySelector('.evidence-footer')).borderTopWidth==='1px')};
- });
- assert.ok(measurements.scrollWidth<=width,JSON.stringify(measurements));
- assert.equal(measurements.emptyHeadings,0);
- assert.equal(measurements.dividers,true);
- assert.equal(measurements.cards.length,3);
- assert.ok(measurements.cards.every(card=>card.scrollHeight<=card.clientHeight+1),'Quote content must not scroll inside a fixed-height card');
- assert.ok(measurements.cards[2].height>measurements.cards[0].height+30,'Long quotation must naturally increase its card height');
- const cols=new Set(measurements.groups.map(group=>group.x)).size;
- assert.equal(cols,width>=1280?2:1,JSON.stringify({width,groups:measurements.groups}));
- const excerpts=await page.locator('[data-evidence-reading] blockquote').allTextContents();
- assert.deepEqual(excerpts,Object.values(raw.artifact[0].report.evidence).map(e=>e.quotes[0].text));
- assert.equal(await page.locator('.source-meta[open]').count(),0);
- const sourceMeta=page.locator('[data-evidence-reading] .source-meta').first();
- await sourceMeta.locator('summary').click();
- const detailsText=await sourceMeta.textContent();
- assert.ok(detailsText.replace(/\s/g,'').includes(raw.artifact[0].report.evidence.R05.quotes[0].ref.replace(/\s/g,'')),detailsText);
- await sourceMeta.locator('summary').click();
- await page.evaluate(()=>window.scrollTo(0,0));
- await page.screenshot({path:path.join(out,'overview-'+width+'.png')});
- await page.locator('[data-evidence-reading]').scrollIntoViewIfNeeded();
- await page.screenshot({path:path.join(out,'evidence-'+width+'.png')});
- if([1440,390].includes(width)) {
- await page.locator('[data-chapter="original-evidence"]').scrollIntoViewIfNeeded();
- await page.screenshot({path:path.join(out,'chapter-'+width+'.png')});
- }
- layouts.push({...measurements,columns:cols});
- checks.push(width+'px:引用卡自适应高度、分隔线、原句完整、出处展开、无横向溢出');
- }
- await page.locator('.finding .evidence-actions button').first().click();
- await page.locator('kf-drawer .panel').waitFor();
- assert.ok((await page.locator('kf-drawer .panel').innerText()).replace(/\s/g,'').includes('R05京东三味六碗商品评价'));
- await page.keyboard.press('Escape');
- await page.locator('kf-drawer .panel').waitFor({state:'hidden'});
- await page.locator('.finding [data-anchor]').first().click();
- await page.locator('kf-drawer .panel').waitFor();
- assert.ok((await page.locator('kf-drawer .panel').innerText()).includes(raw.artifact[0].report.evidence.R05.quotes[0].text));
- checks.push('来源记录与证据链入口仍可用');
- await page.keyboard.press('Escape');
- await page.locator('kf-drawer .panel').waitFor({state:'hidden'});
- await page.setViewportSize({width:1280,height:1050});
- await page.locator('[data-toc] a').nth(2).click();
- await page.waitForURL('**ch=next-test');
- await page.waitForFunction(()=>{
- const r=document.querySelector('kf-app')?.shadowRoot?.querySelector('view-report')?.shadowRoot;
- const target=r?.querySelector('#ch-next-test');return target&&target.getBoundingClientRect().top>0&&target.getBoundingClientRect().top<150;
- });
- checks.push('章节导航定位与表格阅读保持可用');
- await page.goto(origin+'/docs/workshop-report-layout-20261111/preview.html');
- await page.locator('[data-evidence-reading]').waitFor();
- await page.locator('.finding .evidence-actions button').first().click();
- await page.locator('kf-drawer .panel').waitFor();
- await page.keyboard.press('Escape');
- await page.locator('kf-drawer .panel').waitFor({state:'hidden'});
- await page.locator('[data-toc] a').nth(2).click();
- await page.waitForURL('**/preview.html?ch=next-test');
- await page.reload();
- await page.locator('[data-evidence-reading]').waitFor();
- assert.equal(new URL(page.url()).searchParams.get('ch'),'next-test');
- assert.equal(await page.locator('#load-state').count(),0);
- checks.push('独立本地预览可打开、查看来源、切换章节并刷新');
- assert.equal(JSON.stringify(raw),original);
- assert.deepEqual(errors,[]);
- assert.ok(requests.every(item=>['/ksfGetPublicBoard','/ksfGetPublicMemberGroups'].includes(item)));
- await fs.writeFile(path.join(out,'verification.json'),JSON.stringify({ok:true,checks,layouts,errors,fixtureOnly:true,contentUnchanged:true,productionWrites:0},null,2)+'\n');
- console.log(JSON.stringify({ok:true,checks,contentUnchanged:true,productionWrites:0}));
- } finally {await browser.close();}
|