Files
web_hgdjc_h5/tests/hero-swipe.test.mjs
谢宇宁andClaude Opus 5.5 f65d10e998 Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:07:59 +07:00

95 lines
5.6 KiB
JavaScript

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {readFile} from 'node:fs/promises';
import ts from 'typescript';
const sourceUrl=new URL('../src/lib/client/hero-swipe.ts',import.meta.url);
const {outputText}=ts.transpileModule(await readFile(sourceUrl,'utf8'),{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.ESNext}});
const {bindHeroSwipe}=await import('data:text/javascript;base64,'+Buffer.from(outputText+'\n//# sourceURL='+sourceUrl.pathname).toString('base64'));
function fixture(){
const view=new EventTarget(),document=new EventTarget(),root=new EventTarget(),controller=new AbortController();
const classes=new Set();
const hero={classList:{add:name=>classes.add(name),remove:name=>classes.delete(name)}};
document.defaultView=view;document.hidden=false;root.ownerDocument=document;
root.closest=selector=>root.inHero&&(selector==='#hero'||selector==='#hero-feature-carousel'&&root.inCarousel)?root.hero:null;
root.inHero=true;root.inCarousel=true;root.hero=hero;
const state={steps:[],starts:0,ends:0,enabled:true,paused:false};
const options={enabled:()=>state.enabled,onStart(){state.starts++;state.paused=true;},onStep:step=>state.steps.push(step),onEnd(){state.ends++;state.paused=false;}};
bindHeroSwipe(root,options,controller.signal);
function send(target,type,properties={}){
const event=new Event(type,{cancelable:true});
for(const [key,value] of Object.entries({pointerId:1,isPrimary:true,button:0,clientX:150,clientY:100,pointerType:'touch',detail:1,...properties}))Object.defineProperty(event,key,{value});
target.dispatchEvent(event);return event;
}
return {root,view,document,controller,options,state,classes,send,
down:properties=>send(root,'pointerdown',properties),move:properties=>send(view,'pointermove',properties),up:properties=>send(view,'pointerup',properties),click:properties=>send(root,'click',properties)};
}
test('touch and mouse drags switch in both directions and pause autoplay until release',()=>{
for(const pointerType of ['touch','mouse']){
const f=fixture();
f.down({pointerType});assert.equal(f.state.paused,true);
const move=f.move({pointerType,clientX:50});assert.equal(move.defaultPrevented,true);assert.ok(f.classes.has('is-swiping'));
f.up({pointerType,clientX:50});assert.deepEqual(f.state.steps,[1]);assert.equal(f.state.paused,false);assert.equal(f.classes.size,0);
f.down({pointerType});f.move({pointerType,clientX:250});f.up({pointerType,clientX:250});
assert.deepEqual(f.state.steps,[1,-1]);assert.equal(f.state.starts,2);assert.equal(f.state.ends,2);
f.controller.abort();
}
});
test('vertical scrolling, diagonal scrolling, taps, and short gestures do not switch slides',()=>{
const f=fixture();
for(const [clientX,clientY] of [[160,220],[210,170]]){
f.down();assert.equal(f.move({clientX,clientY}).defaultPrevented,false);
f.up({clientX:400,clientY:100});assert.equal(f.state.paused,false);
}
f.down();f.up();assert.equal(f.click().defaultPrevented,false);
f.down();f.move({clientX:120});f.up({clientX:120});
assert.deepEqual(f.state.steps,[]);assert.equal(f.state.starts,f.state.ends);
f.controller.abort();
});
test('dragging the adjacent recommendation cards does not pause or switch the carousel',()=>{
const f=fixture();f.root.inCarousel=false;
f.down();f.move({clientX:50});f.up({clientX:50});
assert.deepEqual(f.state.steps,[]);assert.equal(f.state.starts,0);assert.equal(f.click().defaultPrevented,false);
f.controller.abort();
});
test('swipes suppress the follow-up click without blocking keyboard activation or the next tap',()=>{
const f=fixture();let activated=0;
f.root.addEventListener('click',()=>activated++);
f.down();f.move({clientX:50});f.up({clientX:50});
assert.equal(f.click({detail:0}).defaultPrevented,false);assert.equal(activated,1);
assert.equal(f.click().defaultPrevented,true);assert.equal(activated,1);
f.down();f.up();assert.equal(f.click().defaultPrevented,false);assert.equal(activated,2);
f.down();assert.equal(f.send(f.root,'dragstart').defaultPrevented,true);
f.up();assert.equal(f.send(f.root,'dragstart').defaultPrevented,false);
f.controller.abort();
});
test('cancelled pointers, multitouch, backgrounding, and lost window focus end gestures safely',()=>{
const f=fixture();
const cancel=[()=>f.send(f.view,'pointercancel'),()=>f.down({pointerId:2,isPrimary:false}),()=>{f.document.hidden=true;f.send(f.document,'visibilitychange');},()=>f.send(f.view,'blur')];
for(const action of cancel){
f.document.hidden=false;f.down();f.move({clientX:80});action();f.up({clientX:20});
assert.equal(f.state.paused,false);assert.equal(f.classes.size,0);
}
assert.deepEqual(f.state.steps,[]);assert.equal(f.state.starts,f.state.ends);
f.controller.abort();
});
test('delegated gestures respect availability and replacement content, and route abort removes handlers',()=>{
const f=fixture();
f.state.enabled=false;f.down();f.state.enabled=true;
f.root.inHero=false;f.down();f.root.inHero=true;
f.down({button:2,pointerType:'mouse'});f.down({isPrimary:false});assert.equal(f.state.starts,0);
f.root.hero={classList:{add(){},remove(){}}};
f.down();f.move({clientX:50});f.up({clientX:50});assert.deepEqual(f.state.steps,[1]);
f.down();f.move({clientX:50});f.controller.abort();assert.equal(f.state.paused,false);
f.up({clientX:50});f.down();assert.equal(f.click().defaultPrevented,false);assert.deepEqual(f.state.steps,[1]);
const next=new AbortController();bindHeroSwipe(f.root,f.options,next.signal);
f.down();f.move({clientX:250});f.up({clientX:250});assert.deepEqual(f.state.steps,[1,-1]);next.abort();
});