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(); });