const { test } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const vm = require('node:vm'); const factory = vm.runInNewContext(fs.readFileSync('src/content/dialog-drag.js', 'utf8') + '\ncreateDialogDrag'); function setup({ header = false, interactive = false, unrelated = false } = {}) { const events = {}, winEvents = {}, attrs = {}, style = {}; const panel = { style, isConnected: true, full: false, closest: selector => selector.includes('modal') ? {} : null, querySelector: () => title, setAttribute: (key, value) => attrs[key] = value, removeAttribute: key => delete attrs[key], getClientRects: () => [1], getBoundingClientRect() { const left = 200 + (parseFloat(style.left) || 0), top = 150 + (parseFloat(style.top) || 0); return { left, top, width: this.full ? 1000 : 400, height: this.full ? 800 : 300 }; } }; const title = { closest: () => panel, setPointerCapture() {}, hasPointerCapture: () => true, releasePointerCapture() {} }; const bar = { ...title, contains: node => !unrelated && node === title }; const target = { closest: selector => selector === 'header, h2, h3' ? (header ? bar : title) : interactive ? {} : null }; const document = { documentElement: { clientWidth: 1000, clientHeight: 800 }, addEventListener: (name, fn) => events[name] = fn, querySelectorAll: () => [panel] }; const window = { addEventListener: (name, fn) => winEvents[name] = fn }; factory({ environment: { document, window } }).install(); const event = (x, y) => ({ target, button: 0, pointerId: 1, clientX: x, clientY: y, preventDefault() {} }); return { panel, events, winEvents, attrs, style, document, event }; } test('dragging permits 70 percent overflow in every direction and stops on release', () => { const h = setup(); h.events.pointerdown(h.event(220, 160)); h.events.pointermove(h.event(2000, 2000)); assert.equal(h.panel.getBoundingClientRect().left, 880); assert.equal(h.panel.getBoundingClientRect().top, 710); h.events.pointermove(h.event(-2000, -2000)); assert.equal(h.panel.getBoundingClientRect().left, -280); assert.equal(h.panel.getBoundingClientRect().top, -210); h.events.pointerup(h.event(0, 0)); assert.equal(h.attrs['data-dialog-dragging'], undefined); h.events.pointermove(h.event(400, 400)); assert.equal(h.panel.getBoundingClientRect().left, -280); }); test('full-page panels do not drag', () => { const h = setup(); h.panel.full = true; h.events.pointerdown(h.event(220, 160)); h.events.pointermove(h.event(300, 200)); assert.equal(h.style.left, undefined); }); test('resize keeps at least 30 percent of a moved panel inside each viewport axis', () => { const h = setup(); h.events.pointerdown(h.event(220, 160)); h.events.pointermove(h.event(500, 400)); h.document.documentElement.clientWidth = 600; h.document.documentElement.clientHeight = 500; h.winEvents.resize(); assert.equal(h.panel.getBoundingClientRect().left, 480); assert.equal(h.panel.getBoundingClientRect().top, 390); assert.equal(h.attrs['data-dialog-dragging'], undefined); }); test('empty title bar space starts dragging', () => { const h = setup({ header: true }); h.events.pointerdown(h.event(220, 160)); h.events.pointermove(h.event(300, 200)); assert.equal(h.panel.getBoundingClientRect().left, 280); assert.equal(h.panel.getBoundingClientRect().top, 190); }); test('title bar controls and content headers do not start dragging', () => { for (const options of [{ header: true, interactive: true }, { header: true, unrelated: true }]) { const h = setup(options); h.events.pointerdown(h.event(220, 160)); h.events.pointermove(h.event(300, 200)); assert.equal(h.style.left, undefined); } });