95 lines
5.6 KiB
JavaScript
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();
|
|
});
|