|
| 1 | +// Regression test for issue #44: dragging an EQ/volume slider inside the |
| 2 | +// now-playing footer must not register as a prev/next swipe. |
| 3 | +// Runs the REAL initSwipeGestures source extracted from templates/index.html |
| 4 | +// against the REAL footer markup extracted from the same file. |
| 5 | +const fs = require('fs'); |
| 6 | +const path = require('path'); |
| 7 | +const { JSDOM } = require('jsdom'); |
| 8 | + |
| 9 | +const TEMPLATE = process.argv[2] || path.join(__dirname, '..', 'templates', 'index.html'); |
| 10 | +const html = fs.readFileSync(TEMPLATE, 'utf8'); |
| 11 | + |
| 12 | +// Pull the actual footer markup out of the template |
| 13 | +const footerStart = html.indexOf('<div class="np-bar hidden" id="np-footer">'); |
| 14 | +if (footerStart < 0) throw new Error('np-footer not found'); |
| 15 | +const footerEnd = html.indexOf('<!-- Toast Container -->', footerStart); |
| 16 | +const footerHtml = html.slice(footerStart, footerEnd); |
| 17 | + |
| 18 | +// Pull the actual gesture code out of the template |
| 19 | +// SWIPE_IGNORE_SEL is absent in the pre-fix source; the test then runs the old |
| 20 | +// handler unchanged, which is the point of the regression check. |
| 21 | +const selMatch = html.match(/var SWIPE_IGNORE_SEL=[^\n]*/) || ['']; |
| 22 | +const fnMatch = html.match(/function initSwipeGestures\(\)\{[\s\S]*?\n\}/); |
| 23 | +if (!fnMatch) throw new Error('gesture source not found'); |
| 24 | + |
| 25 | +const dom = new JSDOM(`<body>${footerHtml}</body>`, { |
| 26 | + pretendToBeVisual: true, |
| 27 | + runScripts: 'dangerously', |
| 28 | +}); |
| 29 | +const { window } = dom; |
| 30 | + |
| 31 | +// Evaluate the real source in the jsdom window, with the transport calls |
| 32 | +// stubbed to counters the test can read back off the window. |
| 33 | +window.eval(` |
| 34 | + window.__prev=0; window.__next=0; |
| 35 | + function playerPrev(){window.__prev++} |
| 36 | + function playerNext(){window.__next++} |
| 37 | + var _swipeStartX=0,_swipeStartY=0,_swipeIgnore=false; |
| 38 | + ${selMatch[0]} |
| 39 | + ${fnMatch[0]} |
| 40 | + initSwipeGestures(); |
| 41 | +`); |
| 42 | + |
| 43 | +// jsdom lacks PointerEvent; MouseEvent carries the clientX/clientY the handler reads |
| 44 | +function drag(el, fromX, toX, y = 100) { |
| 45 | + el.dispatchEvent(new window.MouseEvent('pointerdown', { clientX: fromX, clientY: y, bubbles: true })); |
| 46 | + el.dispatchEvent(new window.MouseEvent('pointerup', { clientX: toX, clientY: y, bubbles: true })); |
| 47 | +} |
| 48 | + |
| 49 | +const results = []; |
| 50 | +function check(name, expectPrev, expectNext, fn) { |
| 51 | + window.__prev = 0; window.__next = 0; |
| 52 | + fn(); |
| 53 | + const pass = window.__prev === expectPrev && window.__next === expectNext; |
| 54 | + results.push({ name, pass, got: `prev=${window.__prev} next=${window.__next}`, want: `prev=${expectPrev} next=${expectNext}` }); |
| 55 | +} |
| 56 | + |
| 57 | +const q = (s) => window.document.querySelector(s); |
| 58 | + |
| 59 | +// The reported bug: a long rightward drag on a slider fired playerPrev, |
| 60 | +// which jumped the seek bar backwards. |
| 61 | +check('drag volume slider right', 0, 0, () => drag(q('#eq-volume'), 100, 300)); |
| 62 | +check('drag volume slider left', 0, 0, () => drag(q('#eq-volume'), 300, 100)); |
| 63 | +check('drag bass slider right', 0, 0, () => drag(q('#eq-bass'), 100, 300)); |
| 64 | +check('drag treble slider left', 0, 0, () => drag(q('#eq-treble'), 300, 100)); |
| 65 | +check('drag across the seek bar', 0, 0, () => drag(q('.np-progress-bar'), 100, 300)); |
| 66 | +check('tap a transport button', 0, 0, () => drag(q('#np-play-pause'), 100, 105)); |
| 67 | + |
| 68 | +// The gesture must still work where it was intended: on the footer body. |
| 69 | +check('swipe right on footer body', 1, 0, () => drag(q('#np-footer .np-text'), 100, 300)); |
| 70 | +check('swipe left on footer body', 0, 1, () => drag(q('#np-footer .np-text'), 300, 100)); |
| 71 | +check('short tap on footer body', 0, 0, () => drag(q('#np-footer .np-text'), 100, 110)); |
| 72 | + |
| 73 | +// A drag that leaves the footer mid-gesture must not arm a later swipe. |
| 74 | +check('slider drag cancelled, then real swipe', 1, 0, () => { |
| 75 | + q('#eq-volume').dispatchEvent(new window.MouseEvent('pointerdown', { clientX: 100, clientY: 100, bubbles: true })); |
| 76 | + q('#np-footer').dispatchEvent(new window.MouseEvent('pointercancel', { bubbles: true })); |
| 77 | + drag(q('#np-footer .np-text'), 100, 300); |
| 78 | +}); |
| 79 | + |
| 80 | +let failed = 0; |
| 81 | +for (const r of results) { |
| 82 | + if (!r.pass) failed++; |
| 83 | + console.log(`${r.pass ? 'PASS' : 'FAIL'} ${r.name} (got ${r.got}, want ${r.want})`); |
| 84 | +} |
| 85 | +console.log(failed === 0 ? `\nAll ${results.length} checks passed` : `\n${failed} of ${results.length} FAILED`); |
| 86 | +process.exit(failed === 0 ? 0 : 1); |
0 commit comments