| 1 | // selection.mjs's hash: #<slide>[/<block>], read without ever throwing |
| 2 | // — and its teardown, which is what keeps a resync from leaving a |
| 3 | // second copy of every handler on the page's own elements. |
| 4 | import test from 'node:test' |
| 5 | import assert from 'node:assert/strict' |
| 6 | import { parseHash, formatHash, mountSelection } from './selection.mjs' |
| 7 | import { el, withDocument } from './dom-stub.mjs' |
| 8 | |
| 9 | test('parseHash splits the slide and the block, and survives a stray %', () => { |
| 10 | assert.deepEqual(parseHash('#slideaaaaaaa'), { slide: 'slideaaaaaaa', block: '' }) |
| 11 | assert.deepEqual(parseHash('#slideaaaaaaa/blockbbbbbbb'), { slide: 'slideaaaaaaa', block: 'blockbbbbbbb' }) |
| 12 | assert.deepEqual(parseHash(''), { slide: '', block: '' }) |
| 13 | assert.deepEqual(parseHash('#'), { slide: '', block: '' }) |
| 14 | assert.deepEqual(parseHash('#%'), { slide: '%', block: '' }) |
| 15 | assert.deepEqual(parseHash('#a%2Fb'), { slide: 'a', block: 'b' }) |
| 16 | }) |
| 17 | |
| 18 | // A resync mounts a second selection over the SAME stage element and |
| 19 | // the same document, so the first one has to let go of both. The |
| 20 | // Escape handler is the one that bites: it is on `document`, so a |
| 21 | // leaked one clears a selection nobody is holding any more. |
| 22 | test('destroy takes the listeners off, and a second mount leaves one of each', () => { |
| 23 | const ctx = withDocument() |
| 24 | try { |
| 25 | const stage = el('div', { 'slides-stage': '' }) |
| 26 | const first = mountSelection({ stageEl: stage, getSlideId: () => 'slideaaaaaaa' }) |
| 27 | assert.equal(stage.countListeners('click'), 1) |
| 28 | assert.equal(ctx.document.countListeners('keydown'), 1) |
| 29 | |
| 30 | first.destroy() |
| 31 | assert.equal(stage.countListeners('click'), 0) |
| 32 | assert.equal(ctx.document.countListeners('keydown'), 0) |
| 33 | |
| 34 | const second = mountSelection({ stageEl: stage, getSlideId: () => 'slideaaaaaaa' }) |
| 35 | assert.equal(stage.countListeners('click'), 1, 'one click handler, not one per resync') |
| 36 | assert.equal(ctx.document.countListeners('keydown'), 1) |
| 37 | second.destroy() |
| 38 | } finally { |
| 39 | ctx.restore() |
| 40 | } |
| 41 | }) |
| 42 | |
| 43 | test('formatHash is the inverse', () => { |
| 44 | assert.equal(formatHash('s', 'b'), '#s/b') |
| 45 | assert.equal(formatHash('s', ''), '#s') |
| 46 | assert.equal(formatHash('', 'b'), '') |
| 47 | }) |
| 48 | |