訂判斷及所需輸入
真正 CSS 在 prefers-reduced-motion 停動畫。錯處理等 animationend,令題內已完成動作仍 processing;更正沿所附動作 Promise,結果可完成、失敗或未明。
| 情況 | 輸入 JSON |
|---|---|
| 原例測試 | {"motion":"reduce","outcome":"completed"} |
跑所附比較
瀏覽器真正套兩種動畫偏好並執行處理;標記只屬本地假設動作,動畫完未證明外部業務成功。
| 路徑 | 輸出 JSON |
|---|---|
| 錯誤路徑 | {"state":"processing"} |
| 所需路徑 | {"state":"completed"} |
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
const htmlEscape=s=>String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
async function withPage(action,options={}){
const require=createRequire(import.meta.url);const {chromium}=require('playwright');
const browser=await chromium.launch({headless:true});
try{const context=await browser.newContext(options);const page=await context.newPage();
const requests=[];await page.route('**/*',route=>{requests.push(route.request().url());return route.abort();});
return await action(page,context,requests);
}finally{await browser.close();}
}
const run=async function run(i,corrected=true){
if(!['completed','failed','unknown'].includes(i.outcome)||!['normal','reduce'].includes(i.motion))return {held:true};
return withPage(async page=>{await page.setContent('<style>.animate{animation:pulse 10ms linear}@keyframes pulse{to{opacity:.5}}@media(prefers-reduced-motion:reduce){.animate{animation:none}}</style><div class="progress">Working</div><output id="state">processing</output>');
await page.evaluate(({corrected,outcome})=>{const state=document.querySelector('#state');if(corrected)Promise.resolve(outcome).then(result=>state.textContent=result);else document.querySelector('.progress').addEventListener('animationend',()=>state.textContent='completed');document.querySelector('.progress').classList.add('animate');},{corrected,outcome:i.outcome});
if(corrected||i.motion==='normal')await page.waitForFunction(()=>document.querySelector('#state').textContent!=='processing',{timeout:2000});
return {state:await page.locator('#state').textContent()};
},{reducedMotion:i.motion==='reduce'?'reduce':'no-preference'});
};
const cases=[{"input":{"motion":"reduce","outcome":"completed"},"corrected":false,"expected":{"state":"processing"}},{"input":{"motion":"reduce","outcome":"completed"},"corrected":true,"expected":{"state":"completed"}},{"input":{"motion":"normal","outcome":"completed"},"corrected":true,"expected":{"state":"completed"}},{"input":{"motion":"reduce","outcome":"failed"},"corrected":true,"expected":{"state":"failed"}},{"input":{"motion":"reduce","outcome":"unknown"},"corrected":true,"expected":{"state":"unknown"}}];
for(const test of cases){const actual=await run(test.input,test.corrected);assert.deepEqual(actual,test.expected);console.log(actual);}
按邊界依據作判斷
正常動畫配完成結果可到 completed;減動畫時 failed、unknown 仍各自保留。
- 套所需動畫偏好
- 取動作本身結果
- 更新有名稱的狀態
不能把失敗或未明動作改成視覺成功。
| 條件 | 輸入 JSON | 預期輸出 JSON |
|---|---|---|
| 正常動畫下的動作 | {"motion":"normal","outcome":"completed"} | {"state":"completed"} |
| 真正結果為失敗 | {"motion":"reduce","outcome":"failed"} | {"state":"failed"} |
| 真正結果仍未明 | {"motion":"reduce","outcome":"unknown"} | {"state":"unknown"} |
原例更正後,甚麼驅動完成?
查看參考答案
所附動作結果。animationend 可不出現,不能是唯一完成訊號。