UNPKG

@cycle/dom

Version:

The standard DOM Driver for Cycle.js, based on Snabbdom

1,316 lines (1,189 loc) 38 kB
import {isIE10} from './setup'; import * as assert from 'assert'; import isolate from '@cycle/isolate'; import xs, {Stream, MemoryStream} from 'xstream'; import delay from 'xstream/extra/delay'; import concat from 'xstream/extra/concat'; import {setup} from '@cycle/run'; import { div, textarea, input, span, h2, h3, h4, form, button, makeDOMDriver, DOMSource, MainDOMSource, } from '../../src/index'; function createRenderTarget(id: string | null = null) { const element = document.createElement('div'); element.className = 'cycletest'; if (id) { element.id = id; } document.body.appendChild(element); return element; } function testFragmentEvents() { let captures = false; let bubbles = false; const captureEvent = document.createEvent('CustomEvent'); const bubbleEvent = document.createEvent('CustomEvent'); const fragment = document.createDocumentFragment(); const parent = document.createElement('div'); const child = document.createElement('div'); fragment.appendChild(parent); parent.appendChild(child); parent.addEventListener( 'fragmentCapture', () => { captures = true; }, true ); parent.addEventListener( 'fragmentBubble', () => { bubbles = true; }, false ); captureEvent.initCustomEvent('fragmentCapture', false, true, null); bubbleEvent.initCustomEvent('fragmentBubble', true, true, null); child.dispatchEvent(captureEvent); child.dispatchEvent(bubbleEvent); return {captures, bubbles}; } const fragmentSupport = testFragmentEvents(); describe('DOMSource.events()', function() { it('should catch a basic click interaction Observable', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(h3('.myelementclass', 'Foobar')), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); let dispose: any; sources.DOM.select('.myelementclass') .events('click') .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'Foobar'); dispose(); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: function(root: Element) { const myElement = root.querySelector( '.myelementclass' ) as HTMLElement; assert.notStrictEqual(myElement, null); assert.notStrictEqual(typeof myElement, 'undefined'); assert.strictEqual(myElement.tagName, 'H3'); assert.doesNotThrow(function() { setTimeout(() => myElement.click()); }); }, }); dispose = run(); }); it('should setup click detection with events() after run() occurs', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(h3('.test2.myelementclass', 'Foobar')), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); const dispose = run(); sources.DOM.select('.myelementclass') .events('click') .addListener({ next(ev: Event) { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'Foobar'); dispose(); done(); }, }); setTimeout(() => { const myElement = document.querySelector( '.test2.myelementclass' ) as HTMLElement; assert.notStrictEqual(myElement, null); assert.notStrictEqual(typeof myElement, 'undefined'); assert.strictEqual(myElement.tagName, 'H3'); assert.doesNotThrow(function() { setTimeout(() => myElement.click()); }); }, 200); }); it('should setup click detection on a ready DOM element (e.g. from server)', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.never(), }; } const containerElement = createRenderTarget(); const headerElement = document.createElement('H3'); headerElement.className = 'myelementclass'; headerElement.textContent = 'Foobar'; containerElement.appendChild(headerElement); const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(containerElement), }); const dispose = run(); sources.DOM.select('.myelementclass') .events('click') .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'Foobar'); dispose(); done(); }, }); setTimeout(() => { const myElement = containerElement.querySelector( '.myelementclass' ) as HTMLElement; assert.notStrictEqual(myElement, null); assert.notStrictEqual(typeof myElement, 'undefined'); assert.strictEqual(myElement.tagName, 'H3'); assert.doesNotThrow(function() { setTimeout(() => myElement.click()); }); }, 200); }); it('should catch events using id of root element in DOM.select', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(h3('.myelementclass', 'Foobar')), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget('parent-001')), }); let dispose: any; sources.DOM.select('#parent-001') .events('click') .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'Foobar'); dispose(); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const myElement = root.querySelector( '.myelementclass' ) as HTMLElement; assert.notStrictEqual(myElement, null); assert.notStrictEqual(typeof myElement, 'undefined'); assert.strictEqual(myElement.tagName, 'H3'); assert.doesNotThrow(function() { setTimeout(() => myElement.click()); }); }, }); dispose = run(); }); it('should catch events using id of top element in DOM.select', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(h3('#myElementId', 'Foobar')), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget('parent-002')), }); let dispose: any; sources.DOM.select('#myElementId') .events('click') .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'Foobar'); dispose(); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const myElement = root.querySelector('#myElementId') as HTMLElement; assert.notStrictEqual(myElement, null); assert.notStrictEqual(typeof myElement, 'undefined'); assert.strictEqual(myElement.tagName, 'H3'); assert.doesNotThrow(function() { setTimeout(() => myElement.click()); }); }, }); dispose = run(); }); it('should catch interaction events without prior select()', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div('.parent', [h3('.myelementclass', 'Foobar')])), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); let dispose: any; sources.DOM.events('click').addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'Foobar'); dispose(); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const myElement = root.querySelector( '.myelementclass' ) as HTMLElement; assert.notStrictEqual(myElement, null); assert.notStrictEqual(typeof myElement, 'undefined'); assert.strictEqual(myElement.tagName, 'H3'); assert.doesNotThrow(function() { setTimeout(() => myElement.click()); }); }, }); dispose = run(); }); it('should catch user events using DOM.select().select().events()', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of( h3('.top-most', [ h2('.bar', 'Wrong'), div('.foo', [h4('.bar', 'Correct')]), ]) ), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); let dispose: any; sources.DOM.select('.foo') .select('.bar') .events('click') .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'Correct'); dispose(); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const wrongElement = root.querySelector('.bar') as HTMLElement; const correctElement = root.querySelector('.foo .bar') as HTMLElement; assert.notStrictEqual(wrongElement, null); assert.notStrictEqual(correctElement, null); assert.notStrictEqual(typeof wrongElement, 'undefined'); assert.notStrictEqual(typeof correctElement, 'undefined'); assert.strictEqual(wrongElement.tagName, 'H2'); assert.strictEqual(correctElement.tagName, 'H4'); assert.doesNotThrow(function() { setTimeout(() => wrongElement.click()); setTimeout(() => correctElement.click(), 15); }); }, }); dispose = run(); }); it('should catch events from many elements using DOM.select().events()', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of( div('.parent', [ h4('.clickable.first', 'First'), h4('.clickable.second', 'Second'), ]) ), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); let dispose: any; sources.DOM.select('.clickable') .events('click') .take(1) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'First'); }, }); sources.DOM.select('.clickable') .events('click') .drop(1) .take(1) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'Second'); dispose(); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const firstElem = root.querySelector('.first') as HTMLElement; const secondElem = root.querySelector('.second') as HTMLElement; assert.notStrictEqual(firstElem, null); assert.notStrictEqual(typeof firstElem, 'undefined'); assert.notStrictEqual(secondElem, null); assert.notStrictEqual(typeof secondElem, 'undefined'); assert.doesNotThrow(function() { setTimeout(() => firstElem.click()); setTimeout(() => secondElem.click(), 5); }); }, }); dispose = run(); }); it('should catch interaction events from future elements', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: concat( xs.of(h2('.blesh', 'Blesh')), xs.of(h3('.blish', 'Blish')).compose(delay(150)), xs.of(h4('.blosh', 'Blosh')).compose(delay(150)) ), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget('parent-002')), }); let dispose: any; sources.DOM.select('.blosh') .events('click') .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); assert.strictEqual((ev.target as HTMLElement).textContent, 'Blosh'); dispose(); done(); }, }); sources.DOM.select(':root') .element() .drop(3) .take(1) .addListener({ next: (root: Element) => { const myElement = root.querySelector('.blosh') as HTMLElement; assert.notStrictEqual(myElement, null); assert.notStrictEqual(typeof myElement, 'undefined'); assert.strictEqual(myElement.tagName, 'H4'); assert.strictEqual(myElement.textContent, 'Blosh'); assert.doesNotThrow(function() { setTimeout(() => myElement.click()); }); }, }); dispose = run(); }); it('should catch bubbling events in a DocumentFragment', function(done) { if (isIE10) { done(); return; } const {bubbles: thisBrowserBubblesFragmentEvents} = fragmentSupport; function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div([div('.clickable', 'Hello')])), }; } if (!thisBrowserBubblesFragmentEvents) { done(); } else { const fragment = document.createDocumentFragment(); const renderTarget = fragment.appendChild(document.createElement('div')); const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(renderTarget as Element), }); sources.DOM.select('.clickable') .events('click', {useCapture: false}) .addListener({ next: (ev: Event) => { const elem = ev.target as HTMLElement; assert.strictEqual(ev.type, 'click'); assert.strictEqual(elem.tagName, 'DIV'); assert.strictEqual(elem.className, 'clickable'); assert.strictEqual(elem.textContent, 'Hello'); const top = elem.parentElement as Node; const renderTarget2 = top.parentNode as Node; const frag = renderTarget2.parentNode as Node; assert.strictEqual(frag instanceof DocumentFragment, true); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const clickable = root.querySelector('.clickable') as HTMLElement; setTimeout(() => clickable.click(), 80); }, }); run(); } }); it('should catch non-bubbling events in a DocumentFragment with useCapture', function(done) { if (isIE10) { done(); return; } const {captures: thisBrowserCapturesFragmentEvents} = fragmentSupport; function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div([textarea('.blurable', 'Hello')])), }; } if (!thisBrowserCapturesFragmentEvents) { done(); } else { const fragment = document.createDocumentFragment(); const renderTarget = fragment.appendChild(document.createElement('textarea')); const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(renderTarget as Element), }); sources.DOM.select('.blurable') .events('mouseenter', {useCapture: true}) .addListener({ next: (ev: Event) => { const elem = ev.target as HTMLElement; assert.strictEqual(ev.type, 'mouseenter'); assert.strictEqual(elem.tagName, 'TEXTAREA'); assert.strictEqual(elem.className, 'blurable'); assert.strictEqual(elem.textContent, 'Hello'); const top = elem.parentElement as Node; const renderTarget2 = top.parentNode as Node; const frag = renderTarget2.parentNode as Node; assert.strictEqual(frag instanceof DocumentFragment, true); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const blurable = root.querySelector('.blurable') as HTMLElement; setTimeout(() => { let event; if(typeof(MouseEvent) === 'function') { event = new MouseEvent('mouseenter', { 'view': window, 'bubbles': false, 'cancelable': false }); } else { event = document.createEvent('MouseEvent'); event.initMouseEvent('mouseenter', false, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); } blurable.dispatchEvent(event); }, 80); }, }); run(); } }); it('should have currentTarget or ownerTarget pointed to the selected parent', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of( div('.top', [h2('.parent', [span('.child', 'Hello world')])]) ), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); let dispose: any; sources.DOM.select('.parent') .events('click') .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'SPAN'); assert.strictEqual(target.className, 'child'); assert.strictEqual(target.textContent, 'Hello world'); const currentTarget = ev.currentTarget as HTMLElement; const ownerTarget = (ev as any).ownerTarget as HTMLElement; const currentTargetIsParentH2 = currentTarget.tagName === 'H2' && currentTarget.className === 'parent'; const ownerTargetIsParentH2 = ownerTarget.tagName === 'H2' && ownerTarget.className === 'parent'; assert.strictEqual( currentTargetIsParentH2 || ownerTargetIsParentH2, true ); dispose(); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const child = root.querySelector('.child') as HTMLElement; assert.notStrictEqual(child, null); assert.notStrictEqual(typeof child, 'undefined'); assert.strictEqual(child.tagName, 'SPAN'); assert.strictEqual(child.className, 'child'); assert.doesNotThrow(function() { setTimeout(() => child.click()); }); }, }); dispose = run(); }); it('should catch a non-bubbling Form `reset` event', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of( div('.parent', [form('.form', [input('.field', {type: 'text'})])]) ), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.form') .events('reset', {}, false) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'reset'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'FORM'); assert.strictEqual(target.className, 'form'); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const _form = root.querySelector('.form') as HTMLFormElement; setTimeout(() => _form.reset()); }, }); run(); }); it('should catch a non-bubbling click event with useCapture', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div('.parent', [div('.clickable', 'Hello')])), }; } function click(el: Element): void { const ev = document.createEvent(`MouseEvent`); ev.initMouseEvent( `click`, false, // bubble true, // cancelable window, 0, 0, 0, 0, 0, // coordinates false, false, false, false, // modifier keys 0, //left null ); el.dispatchEvent(ev); } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.clickable') .events('click', {useCapture: true}, false) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'DIV'); assert.strictEqual(target.className, 'clickable'); assert.strictEqual(target.textContent, 'Hello'); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const clickable = root.querySelector('.clickable') as HTMLElement; setTimeout(() => click(clickable)); }, }); run(); }); it('should emit to multiple nonbubbling event streams', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of( div('.parent', [ input('.input', {type: 'text'}), ]) ), }; } if (!document.hasFocus()) { done(); } else { const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); Stream.merge( sources.DOM.select('.input').events('focus'), sources.DOM.select('.input').events('focus'), ) .mapTo(1) .fold((prev, current)=> prev + current, 0) .addListener({ next: (count) => { if (count === 2) { done(); } }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const inputElement = root.querySelector('.input') as HTMLElement; setTimeout(() => inputElement.focus(), 50); }, }); run(); } }); it('should catch a blur event with useCapture', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of( div('.parent', [ input('.correct', {type: 'text'}, []), input('.wrong', {type: 'text'}, []), input('.dummy', {type: 'text'}), ]) ), }; } if (!document.hasFocus()) { done(); } else { const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.correct') .events('blur', {useCapture: true}) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'blur'); assert.strictEqual((ev.target as HTMLElement).className, 'correct'); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const correct = root.querySelector('.correct') as HTMLElement; const wrong = root.querySelector('.wrong') as HTMLElement; const dummy = root.querySelector('.dummy') as HTMLElement; setTimeout(() => wrong.focus(), 50); setTimeout(() => dummy.focus(), 100); setTimeout(() => correct.focus(), 150); setTimeout(() => dummy.focus(), 200); }, }); run(); } }); it('should catch a blur event by default (no options)', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of( div('.parent', [ input('.correct', {type: 'text'}, []), input('.wrong', {type: 'text'}, []), input('.dummy', {type: 'text'}), ]) ), }; } if (!document.hasFocus()) { done(); } else { const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.correct') .events('blur') .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'blur'); assert.strictEqual((ev.target as HTMLElement).className, 'correct'); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const correct = root.querySelector('.correct') as HTMLElement; const wrong = root.querySelector('.wrong') as HTMLElement; const dummy = root.querySelector('.dummy') as HTMLElement; setTimeout(() => wrong.focus(), 50); setTimeout(() => dummy.focus(), 100); setTimeout(() => correct.focus(), 150); setTimeout(() => dummy.focus(), 200); }, }); run(); } }); it('should not simulate bubbling for non-bubbling events', done => { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of( div('.parent', [form('.form', [input('.field', {type: 'text'})])]) ), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.parent') .events('reset') .addListener({ next: (ev: Event) => { done(new Error('Reset event should not bubble to parent')); }, }); sources.DOM.select('.form') .events('reset') .compose(delay(200)) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'reset'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'FORM'); assert.strictEqual(target.className, 'form'); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const _form = root.querySelector('.form') as HTMLFormElement; setTimeout(() => _form.reset()); }, }); run(); }); it('should have the DevTools flag in the source stream', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(h3('.myelementclass', 'Foobar')), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); const event$ = sources.DOM.select('.myelementclass').events('click'); assert.strictEqual((event$ as any)._isCycleSource, 'DOM'); done(); }); it('should allow restarting of event streams from isolated components', function(done) { const outSubject = xs.create<any>(); const switchSubject = xs.create<any>(); function component(_sources: {DOM: DOMSource}) { const itemMouseDown$ = _sources.DOM.select('.item').events( 'mousedown', {}, false ); const itemMouseUp$ = _sources.DOM.select('.item').events( 'mouseup', {}, false ); const itemMouseClick$ = itemMouseDown$ .map(down => itemMouseUp$.filter(up => down.target === up.target)) .flatten(); switchSubject .map(() => itemMouseClick$) .flatten() .addListener({ next: (ev: any) => { outSubject.shamefullySendNext(ev); }, }); return { DOM: xs.of(button('.item', ['stuff'])), }; } function app(_sources: {DOM: DOMSource}) { return isolate(component)(_sources); } function mouseevent(el: Element, type: string) { // This works on IE10 const ev = document.createEvent('MouseEvent'); ev.initMouseEvent( type, false, // bubble true, // cancelable window, 0, 0, 0, 0, 0, // coordinates false, false, false, false, // modifier keys 0, //left null ); // Would rather user this line below but does not work on IE10 //const ev = new MouseEvent(type) el.dispatchEvent(ev); } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); let count = 0; outSubject.addListener({ next: (ev: any) => { assert.strictEqual(ev.type, 'mouseup'); count++; if (count === 2) { done(); } }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const clickable = root.querySelector('.item') as HTMLElement; setTimeout(() => switchSubject.shamefullySendNext(null)); setTimeout(() => mouseevent(clickable, 'mousedown'), 100); setTimeout(() => mouseevent(clickable, 'mouseup'), 200); setTimeout(() => switchSubject.shamefullySendNext(null), 300); setTimeout(() => mouseevent(clickable, 'mousedown'), 400); setTimeout(() => mouseevent(clickable, 'mouseup'), 500); }, }); run(); }); it('should allow preventing default event behavior', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div('.parent', [button('.button')])), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.button') .events('click', {preventDefault: true}) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'BUTTON'); assert.strictEqual(target.className, 'button'); assert.strictEqual(ev.defaultPrevented, true); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const _button = root.querySelector('.button') as HTMLButtonElement; setTimeout(() => _button.click()); }, }); run(); }); it('should allow preventing default event behavior with function', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div('.parent', [button('.button')])), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.button') .events('click', {preventDefault: (ev: any) => ev.type === 'click'}) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'BUTTON'); assert.strictEqual(target.className, 'button'); assert.strictEqual(ev.defaultPrevented, true); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const _button = root.querySelector('.button') as HTMLButtonElement; setTimeout(() => _button.click()); }, }); run(); }); it('should allow preventing default event behavior with object', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div('.parent', [button('.button')])), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.button') .events('click', {preventDefault: {type: 'click'}}) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'BUTTON'); assert.strictEqual(target.className, 'button'); assert.strictEqual(ev.defaultPrevented, true); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const _button = root.querySelector('.button') as HTMLButtonElement; setTimeout(() => _button.click()); }, }); run(); }); it('should allow preventing default event behavior with array in object', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div('.parent', [button('.button.to-prevent')])), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.button') .events('click', { preventDefault: {target: {classList: ['button', 'to-prevent']}}, }) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'BUTTON'); assert.strictEqual(target.className, 'button to-prevent'); assert.strictEqual(ev.defaultPrevented, true); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const _button = root.querySelector('.button') as HTMLButtonElement; setTimeout(() => _button.click()); }, }); run(); }); it('should not prevent default on returning false from function predicate', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div('.parent', [button('.button')])), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.button') .events('click', {preventDefault: (ev: any) => ev.type !== 'click'}) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'BUTTON'); assert.strictEqual(target.className, 'button'); assert.strictEqual(ev.defaultPrevented, false); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const _button = root.querySelector('.button') as HTMLButtonElement; setTimeout(() => _button.click()); }, }); run(); }); it('should not prevent default on returning false from object predicate', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div('.parent', [button('.button')])), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.button') .events('click', {preventDefault: {type: 'notClick'}}) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'BUTTON'); assert.strictEqual(target.className, 'button'); assert.strictEqual(ev.defaultPrevented, false); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const _button = root.querySelector('.button') as HTMLButtonElement; setTimeout(() => _button.click()); }, }); run(); }); it('should not prevent default on returning false from array-in-object predicate', function(done) { function app(_sources: {DOM: DOMSource}) { return { DOM: xs.of(div('.parent', [button('.button.to-prevent')])), }; } const {sinks, sources, run} = setup(app, { DOM: makeDOMDriver(createRenderTarget()), }); sources.DOM.select('.button') .events('click', { preventDefault: {target: {classList: ['button', 'missing-class']}}, }) .addListener({ next: (ev: Event) => { assert.strictEqual(ev.type, 'click'); const target = ev.target as HTMLElement; assert.strictEqual(target.tagName, 'BUTTON'); assert.strictEqual(target.className, 'button to-prevent'); assert.strictEqual(ev.defaultPrevented, false); done(); }, }); sources.DOM.select(':root') .element() .drop(1) .take(1) .addListener({ next: (root: Element) => { const _button = root.querySelector('.button') as HTMLButtonElement; setTimeout(() => _button.click()); }, }); run(); }); });