diff options
Diffstat (limited to 'js/tests/unit/dom')
| -rw-r--r-- | js/tests/unit/dom/data.spec.js | 151 | ||||
| -rw-r--r-- | js/tests/unit/dom/event-handler.spec.js | 98 | ||||
| -rw-r--r-- | js/tests/unit/dom/manipulator.spec.js | 54 | ||||
| -rw-r--r-- | js/tests/unit/dom/selector-engine.spec.js | 82 |
4 files changed, 293 insertions, 92 deletions
diff --git a/js/tests/unit/dom/data.spec.js b/js/tests/unit/dom/data.spec.js index c80f32db0..a00d3b734 100644 --- a/js/tests/unit/dom/data.spec.js +++ b/js/tests/unit/dom/data.spec.js @@ -4,128 +4,103 @@ import Data from '../../../src/dom/data' import { getFixture, clearFixture } from '../../helpers/fixture' describe('Data', () => { + const TEST_KEY = 'bs.test' + const UNKNOWN_KEY = 'bs.unknown' + const TEST_DATA = { + test: 'bsData' + } + let fixtureEl + let div beforeAll(() => { fixtureEl = getFixture() }) + beforeEach(() => { + fixtureEl.innerHTML = '<div></div>' + div = fixtureEl.querySelector('div') + }) + afterEach(() => { + Data.remove(div, TEST_KEY) clearFixture() }) - describe('setData', () => { - it('should set data in an element by adding a bsKey attribute', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } - - Data.setData(div, 'test', data) - expect(div.bsKey).toBeDefined() - }) + it('should return null for unknown elements', () => { + const data = { ...TEST_DATA } - it('should change data if something is already stored', () => { - fixtureEl.innerHTML = '<div></div>' + Data.set(div, TEST_KEY, data) - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } - - Data.setData(div, 'test', data) - - data.test = 'bsData2' - Data.setData(div, 'test', data) - - expect(div.bsKey).toBeDefined() - }) + expect(Data.get(null)).toBeNull() + expect(Data.get(undefined)).toBeNull() + expect(Data.get(document.createElement('div'), TEST_KEY)).toBeNull() }) - describe('getData', () => { - it('should return stored data', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } + it('should return null for unknown keys', () => { + const data = { ...TEST_DATA } - Data.setData(div, 'test', data) - expect(Data.getData(div, 'test')).toEqual(data) - }) + Data.set(div, TEST_KEY, data) - it('should return null on undefined element', () => { - expect(Data.getData(null)).toEqual(null) - expect(Data.getData(undefined)).toEqual(null) - }) - - it('should return null when an element have nothing stored', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - expect(Data.getData(div, 'test')).toEqual(null) - }) - - it('should return null when an element have nothing stored with the provided key', () => { - fixtureEl.innerHTML = '<div></div>' + expect(Data.get(div, null)).toBeNull() + expect(Data.get(div, undefined)).toBeNull() + expect(Data.get(div, UNKNOWN_KEY)).toBeNull() + }) - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } + it('should store data for an element with a given key and return it', () => { + const data = { ...TEST_DATA } - Data.setData(div, 'test', data) + Data.set(div, TEST_KEY, data) - expect(Data.getData(div, 'test2')).toEqual(null) - }) + expect(Data.get(div, TEST_KEY)).toBe(data) }) - describe('removeData', () => { - it('should do nothing when an element have nothing stored', () => { - fixtureEl.innerHTML = '<div></div>' + it('should overwrite data if something is already stored', () => { + const data = { ...TEST_DATA } + const copy = { ...data } - const div = fixtureEl.querySelector('div') + Data.set(div, TEST_KEY, data) + Data.set(div, TEST_KEY, copy) - Data.removeData(div, 'test') - expect().nothing() - }) + expect(Data.get(div, TEST_KEY)).not.toBe(data) + expect(Data.get(div, TEST_KEY)).toBe(copy) + }) - it('should should do nothing if it\'s not a valid key provided', () => { - fixtureEl.innerHTML = '<div></div>' + it('should do nothing when an element have nothing stored', () => { + Data.remove(div, TEST_KEY) - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } + expect().nothing() + }) - Data.setData(div, 'test', data) + it('should remove nothing for an unknown key', () => { + const data = { ...TEST_DATA } - expect(div.bsKey).toBeDefined() + Data.set(div, TEST_KEY, data) + Data.remove(div, UNKNOWN_KEY) - Data.removeData(div, 'test2') + expect(Data.get(div, TEST_KEY)).toBe(data) + }) - expect(div.bsKey).toBeDefined() - }) + it('should remove data for a given key', () => { + const data = { ...TEST_DATA } - it('should remove data if something is stored', () => { - fixtureEl.innerHTML = '<div></div>' + Data.set(div, TEST_KEY, data) + Data.remove(div, TEST_KEY) - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } + expect(Data.get(div, TEST_KEY)).toBeNull() + }) - Data.setData(div, 'test', data) + it('should console.error a message if called with multiple keys', () => { + /* eslint-disable no-console */ + console.error = jasmine.createSpy('console.error') - expect(div.bsKey).toBeDefined() + const data = { ...TEST_DATA } + const copy = { ...data } - Data.removeData(div, 'test') + Data.set(div, TEST_KEY, data) + Data.set(div, UNKNOWN_KEY, copy) - expect(div.bsKey).toBeUndefined() - }) + expect(console.error).toHaveBeenCalled() + expect(Data.get(div, UNKNOWN_KEY)).toBe(null) }) }) diff --git a/js/tests/unit/dom/event-handler.spec.js b/js/tests/unit/dom/event-handler.spec.js index e596a49b5..45f2d6e55 100644 --- a/js/tests/unit/dom/event-handler.spec.js +++ b/js/tests/unit/dom/event-handler.spec.js @@ -77,10 +77,78 @@ describe('EventHandler', () => { div.click() }) + + it('should handle mouseenter/mouseleave like the native counterpart', done => { + fixtureEl.innerHTML = [ + '<div class="outer">', + '<div class="inner">', + '<div class="nested">', + '<div class="deep"></div>', + '</div>', + '</div>', + '<div class="sibling"></div>', + '</div>' + ] + + const outer = fixtureEl.querySelector('.outer') + const inner = fixtureEl.querySelector('.inner') + const nested = fixtureEl.querySelector('.nested') + const deep = fixtureEl.querySelector('.deep') + const sibling = fixtureEl.querySelector('.sibling') + + const enterSpy = jasmine.createSpy('mouseenter') + const leaveSpy = jasmine.createSpy('mouseleave') + const delegateEnterSpy = jasmine.createSpy('mouseenter') + const delegateLeaveSpy = jasmine.createSpy('mouseleave') + + EventHandler.on(inner, 'mouseenter', enterSpy) + EventHandler.on(inner, 'mouseleave', leaveSpy) + EventHandler.on(outer, 'mouseenter', '.inner', delegateEnterSpy) + EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy) + + EventHandler.on(sibling, 'mouseenter', () => { + expect(enterSpy.calls.count()).toBe(2) + expect(leaveSpy.calls.count()).toBe(2) + expect(delegateEnterSpy.calls.count()).toBe(2) + expect(delegateLeaveSpy.calls.count()).toBe(2) + done() + }) + + const moveMouse = (from, to) => { + from.dispatchEvent(new MouseEvent('mouseout', { + bubbles: true, + relatedTarget: to + })) + + to.dispatchEvent(new MouseEvent('mouseover', { + bubbles: true, + relatedTarget: from + })) + } + + // from outer to deep and back to outer (nested) + moveMouse(outer, inner) + moveMouse(inner, nested) + moveMouse(nested, deep) + moveMouse(deep, nested) + moveMouse(nested, inner) + moveMouse(inner, outer) + + setTimeout(() => { + expect(enterSpy.calls.count()).toBe(1) + expect(leaveSpy.calls.count()).toBe(1) + expect(delegateEnterSpy.calls.count()).toBe(1) + expect(delegateLeaveSpy.calls.count()).toBe(1) + + // from outer to inner to sibling (adjacent) + moveMouse(outer, inner) + moveMouse(inner, sibling) + }, 20) + }) }) describe('one', () => { - it('should call listener just one', done => { + it('should call listener just once', done => { fixtureEl.innerHTML = '<div></div>' let called = 0 @@ -101,6 +169,28 @@ describe('EventHandler', () => { done() }, 20) }) + + it('should call delegated listener just once', done => { + fixtureEl.innerHTML = '<div></div>' + + let called = 0 + const div = fixtureEl.querySelector('div') + const obj = { + oneListener() { + called++ + } + } + + EventHandler.one(fixtureEl, 'bootstrap', 'div', obj.oneListener) + + EventHandler.trigger(div, 'bootstrap') + EventHandler.trigger(div, 'bootstrap') + + setTimeout(() => { + expect(called).toEqual(1) + done() + }, 20) + }) }) describe('off', () => { @@ -278,10 +368,10 @@ describe('EventHandler', () => { it('should remove the correct delegated event listener', () => { const element = document.createElement('div') const subelement = document.createElement('span') - element.appendChild(subelement) + element.append(subelement) const anchor = document.createElement('a') - element.appendChild(anchor) + element.append(anchor) let i = 0 const handler = () => { @@ -291,7 +381,7 @@ describe('EventHandler', () => { EventHandler.on(element, 'click', 'a', handler) EventHandler.on(element, 'click', 'span', handler) - fixtureEl.appendChild(element) + fixtureEl.append(element) EventHandler.trigger(anchor, 'click') EventHandler.trigger(subelement, 'click') diff --git a/js/tests/unit/dom/manipulator.spec.js b/js/tests/unit/dom/manipulator.spec.js index 3d91e6f74..13d0c3d17 100644 --- a/js/tests/unit/dom/manipulator.spec.js +++ b/js/tests/unit/dom/manipulator.spec.js @@ -119,6 +119,60 @@ describe('Manipulator', () => { expect(offset.top).toEqual(jasmine.any(Number)) expect(offset.left).toEqual(jasmine.any(Number)) }) + + it('should return offset relative to attached element\'s offset', () => { + const top = 500 + const left = 1000 + + fixtureEl.innerHTML = `<div style="position:absolute;top:${top}px;left:${left}px"></div>` + + const div = fixtureEl.querySelector('div') + const offset = Manipulator.offset(div) + const fixtureOffset = Manipulator.offset(fixtureEl) + + expect(offset).toEqual({ + top: fixtureOffset.top + top, + left: fixtureOffset.left + left + }) + }) + + it('should not change offset when viewport is scrolled', done => { + const top = 500 + const left = 1000 + const scrollY = 200 + const scrollX = 400 + + fixtureEl.innerHTML = `<div style="position:absolute;top:${top}px;left:${left}px"></div>` + + const div = fixtureEl.querySelector('div') + const offset = Manipulator.offset(div) + + // append an element that forces scrollbars on the window so we can scroll + const { defaultView: win, body } = fixtureEl.ownerDocument + const forceScrollBars = document.createElement('div') + forceScrollBars.style.cssText = 'position:absolute;top:5000px;left:5000px;width:1px;height:1px' + body.append(forceScrollBars) + + const scrollHandler = () => { + expect(window.pageYOffset).toBe(scrollY) + expect(window.pageXOffset).toBe(scrollX) + + const newOffset = Manipulator.offset(div) + + expect(newOffset).toEqual({ + top: offset.top, + left: offset.left + }) + + win.removeEventListener('scroll', scrollHandler) + forceScrollBars.remove() + win.scrollTo(0, 0) + done() + } + + win.addEventListener('scroll', scrollHandler) + win.scrollTo(scrollX, scrollY) + }) }) describe('position', () => { diff --git a/js/tests/unit/dom/selector-engine.spec.js b/js/tests/unit/dom/selector-engine.spec.js index d108a2efb..08c3ae818 100644 --- a/js/tests/unit/dom/selector-engine.spec.js +++ b/js/tests/unit/dom/selector-engine.spec.js @@ -156,5 +156,87 @@ describe('SelectorEngine', () => { expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn]) }) }) + + describe('focusableChildren', () => { + it('should return only elements with specific tag names', () => { + fixtureEl.innerHTML = [ + '<div>lorem</div>', + '<span>lorem</span>', + '<a>lorem</a>', + '<button>lorem</button>', + '<input />', + '<textarea></textarea>', + '<select></select>', + '<details>lorem</details>' + ].join('') + + const expectedElements = [ + fixtureEl.querySelector('a'), + fixtureEl.querySelector('button'), + fixtureEl.querySelector('input'), + fixtureEl.querySelector('textarea'), + fixtureEl.querySelector('select'), + fixtureEl.querySelector('details') + ] + + expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements) + }) + + it('should return any element with non negative tab index', () => { + fixtureEl.innerHTML = [ + '<div tabindex>lorem</div>', + '<div tabindex="0">lorem</div>', + '<div tabindex="10">lorem</div>' + ].join('') + + const expectedElements = [ + fixtureEl.querySelector('[tabindex]'), + fixtureEl.querySelector('[tabindex="0"]'), + fixtureEl.querySelector('[tabindex="10"]') + ] + + expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements) + }) + + it('should return not return elements with negative tab index', () => { + fixtureEl.innerHTML = [ + '<button tabindex="-1">lorem</button>' + ].join('') + + const expectedElements = [] + + expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements) + }) + + it('should return contenteditable elements', () => { + fixtureEl.innerHTML = [ + '<div contenteditable="true">lorem</div>' + ].join('') + + const expectedElements = [fixtureEl.querySelector('[contenteditable="true"]')] + + expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements) + }) + + it('should not return disabled elements', () => { + fixtureEl.innerHTML = [ + '<button disabled="true">lorem</button>' + ].join('') + + const expectedElements = [] + + expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements) + }) + + it('should not return invisible elements', () => { + fixtureEl.innerHTML = [ + '<button style="display:none;">lorem</button>' + ].join('') + + const expectedElements = [] + + expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements) + }) + }) }) |
