aboutsummaryrefslogtreecommitdiff
path: root/js/tests/unit/dom
diff options
context:
space:
mode:
authorXhmikosR <[email protected]>2021-08-18 07:29:56 +0300
committerGitHub <[email protected]>2021-08-18 07:29:56 +0300
commit433a148c9e61aa942801fd8101dfa5c4045fdaed (patch)
treef41db59fd06019169df5ea0338213ec0e298f226 /js/tests/unit/dom
parentb97cfa163b5098db70e03b27c91fca5dde9c267e (diff)
parent18b3e1ac71f73d006756684a285c5a818e2d1454 (diff)
downloadbootstrap-global-focus-vars.tar.xz
bootstrap-global-focus-vars.zip
Merge branch 'main' into global-focus-varsglobal-focus-vars
Diffstat (limited to 'js/tests/unit/dom')
-rw-r--r--js/tests/unit/dom/data.spec.js151
-rw-r--r--js/tests/unit/dom/event-handler.spec.js98
-rw-r--r--js/tests/unit/dom/manipulator.spec.js54
-rw-r--r--js/tests/unit/dom/selector-engine.spec.js82
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)
+ })
+ })
})