aboutsummaryrefslogtreecommitdiff
path: root/js/tests/unit/util
diff options
context:
space:
mode:
authorXhmikosR <[email protected]>2021-09-07 21:49:51 +0300
committerGitHub <[email protected]>2021-09-07 21:49:51 +0300
commit65413de3131294cbf7bc8bff94914cc8062149c6 (patch)
treed606f927ef9d5e505245b0fce29366979d219f83 /js/tests/unit/util
parent72e79d0e3997c3a850263880240f26684b9784f7 (diff)
parent0d81d3cbc14dfcdca8a868e3f25189a4f1ab273c (diff)
downloadbootstrap-docs-offcanvas-navbar.tar.xz
bootstrap-docs-offcanvas-navbar.zip
Merge branch 'main' into docs-offcanvas-navbardocs-offcanvas-navbar
Diffstat (limited to 'js/tests/unit/util')
-rw-r--r--js/tests/unit/util/backdrop.spec.js4
-rw-r--r--js/tests/unit/util/component-functions.spec.js108
-rw-r--r--js/tests/unit/util/focustrap.spec.js210
-rw-r--r--js/tests/unit/util/index.spec.js5
4 files changed, 323 insertions, 4 deletions
diff --git a/js/tests/unit/util/backdrop.spec.js b/js/tests/unit/util/backdrop.spec.js
index 59b789071..b885b60b5 100644
--- a/js/tests/unit/util/backdrop.spec.js
+++ b/js/tests/unit/util/backdrop.spec.js
@@ -18,7 +18,7 @@ describe('Backdrop', () => {
const list = document.querySelectorAll(CLASS_BACKDROP)
list.forEach(el => {
- document.body.removeChild(el)
+ el.remove()
})
})
@@ -141,7 +141,7 @@ describe('Backdrop', () => {
const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
instance.show(() => {
- wrapper.parentNode.removeChild(wrapper)
+ wrapper.remove()
instance.hide(() => {
expect(getElements().length).toEqual(0)
done()
diff --git a/js/tests/unit/util/component-functions.spec.js b/js/tests/unit/util/component-functions.spec.js
new file mode 100644
index 000000000..edaedd32e
--- /dev/null
+++ b/js/tests/unit/util/component-functions.spec.js
@@ -0,0 +1,108 @@
+/* Test helpers */
+
+import { clearFixture, createEvent, getFixture } from '../../helpers/fixture'
+import { enableDismissTrigger } from '../../../src/util/component-functions'
+import BaseComponent from '../../../src/base-component'
+
+class DummyClass2 extends BaseComponent {
+ static get NAME() {
+ return 'test'
+ }
+
+ hide() {
+ return true
+ }
+
+ testMethod() {
+ return true
+ }
+}
+
+describe('Plugin functions', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('data-bs-dismiss functionality', () => {
+ it('should get Plugin and execute the given method, when a click occurred on data-bs-dismiss="PluginName"', () => {
+ fixtureEl.innerHTML = [
+ '<div id="foo" class="test">',
+ ' <button type="button" data-bs-dismiss="test" data-bs-target="#foo"></button>',
+ '</div>'
+ ].join('')
+
+ spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
+ spyOn(DummyClass2.prototype, 'testMethod')
+ const componentWrapper = fixtureEl.querySelector('#foo')
+ const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
+ const event = createEvent('click')
+
+ enableDismissTrigger(DummyClass2, 'testMethod')
+ btnClose.dispatchEvent(event)
+
+ expect(DummyClass2.getOrCreateInstance).toHaveBeenCalledWith(componentWrapper)
+ expect(DummyClass2.prototype.testMethod).toHaveBeenCalled()
+ })
+
+ it('if data-bs-dismiss="PluginName" hasn\'t got "data-bs-target", "getOrCreateInstance" has to be initialized by closest "plugin.Name" class', () => {
+ fixtureEl.innerHTML = [
+ '<div id="foo" class="test">',
+ ' <button type="button" data-bs-dismiss="test"></button>',
+ '</div>'
+ ].join('')
+
+ spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
+ spyOn(DummyClass2.prototype, 'hide')
+ const componentWrapper = fixtureEl.querySelector('#foo')
+ const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
+ const event = createEvent('click')
+
+ enableDismissTrigger(DummyClass2)
+ btnClose.dispatchEvent(event)
+
+ expect(DummyClass2.getOrCreateInstance).toHaveBeenCalledWith(componentWrapper)
+ expect(DummyClass2.prototype.hide).toHaveBeenCalled()
+ })
+
+ it('if data-bs-dismiss="PluginName" is disabled, must not trigger function', () => {
+ fixtureEl.innerHTML = [
+ '<div id="foo" class="test">',
+ ' <button type="button" disabled data-bs-dismiss="test"></button>',
+ '</div>'
+ ].join('')
+
+ spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
+ const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
+ const event = createEvent('click')
+
+ enableDismissTrigger(DummyClass2)
+ btnClose.dispatchEvent(event)
+
+ expect(DummyClass2.getOrCreateInstance).not.toHaveBeenCalled()
+ })
+
+ it('should prevent default when the trigger is <a> or <area>', () => {
+ fixtureEl.innerHTML = [
+ '<div id="foo" class="test">',
+ ' <a type="button" data-bs-dismiss="test"></a>',
+ '</div>'
+ ].join('')
+
+ const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
+ const event = createEvent('click')
+
+ enableDismissTrigger(DummyClass2)
+ spyOn(Event.prototype, 'preventDefault').and.callThrough()
+
+ btnClose.dispatchEvent(event)
+
+ expect(Event.prototype.preventDefault).toHaveBeenCalled()
+ })
+ })
+})
diff --git a/js/tests/unit/util/focustrap.spec.js b/js/tests/unit/util/focustrap.spec.js
new file mode 100644
index 000000000..99bc95fca
--- /dev/null
+++ b/js/tests/unit/util/focustrap.spec.js
@@ -0,0 +1,210 @@
+import FocusTrap from '../../../src/util/focustrap'
+import EventHandler from '../../../src/dom/event-handler'
+import SelectorEngine from '../../../src/dom/selector-engine'
+import { clearFixture, getFixture, createEvent } from '../../helpers/fixture'
+
+describe('FocusTrap', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('activate', () => {
+ it('should autofocus itself by default', () => {
+ fixtureEl.innerHTML = '<div id="focustrap" tabindex="-1"></div>'
+
+ const trapElement = fixtureEl.querySelector('div')
+
+ spyOn(trapElement, 'focus')
+
+ const focustrap = new FocusTrap({ trapElement })
+ focustrap.activate()
+
+ expect(trapElement.focus).toHaveBeenCalled()
+ })
+
+ it('if configured not to autofocus, should not autofocus itself', () => {
+ fixtureEl.innerHTML = '<div id="focustrap" tabindex="-1"></div>'
+
+ const trapElement = fixtureEl.querySelector('div')
+
+ spyOn(trapElement, 'focus')
+
+ const focustrap = new FocusTrap({ trapElement, autofocus: false })
+ focustrap.activate()
+
+ expect(trapElement.focus).not.toHaveBeenCalled()
+ })
+
+ it('should force focus inside focus trap if it can', done => {
+ fixtureEl.innerHTML = [
+ '<a href="#" id="outside">outside</a>',
+ '<div id="focustrap" tabindex="-1">',
+ ' <a href="#" id="inside">inside</a>',
+ '</div>'
+ ].join('')
+
+ const trapElement = fixtureEl.querySelector('div')
+ const focustrap = new FocusTrap({ trapElement })
+ focustrap.activate()
+
+ const inside = document.getElementById('inside')
+
+ const focusInListener = () => {
+ expect(inside.focus).toHaveBeenCalled()
+ document.removeEventListener('focusin', focusInListener)
+ done()
+ }
+
+ spyOn(inside, 'focus')
+ spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [inside])
+
+ document.addEventListener('focusin', focusInListener)
+
+ const focusInEvent = createEvent('focusin', { bubbles: true })
+ Object.defineProperty(focusInEvent, 'target', {
+ value: document.getElementById('outside')
+ })
+
+ document.dispatchEvent(focusInEvent)
+ })
+
+ it('should wrap focus around forward on tab', done => {
+ fixtureEl.innerHTML = [
+ '<a href="#" id="outside">outside</a>',
+ '<div id="focustrap" tabindex="-1">',
+ ' <a href="#" id="first">first</a>',
+ ' <a href="#" id="inside">inside</a>',
+ ' <a href="#" id="last">last</a>',
+ '</div>'
+ ].join('')
+
+ const trapElement = fixtureEl.querySelector('div')
+ const focustrap = new FocusTrap({ trapElement })
+ focustrap.activate()
+
+ const first = document.getElementById('first')
+ const inside = document.getElementById('inside')
+ const last = document.getElementById('last')
+ const outside = document.getElementById('outside')
+
+ spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
+ spyOn(first, 'focus').and.callThrough()
+
+ const focusInListener = () => {
+ expect(first.focus).toHaveBeenCalled()
+ first.removeEventListener('focusin', focusInListener)
+ done()
+ }
+
+ first.addEventListener('focusin', focusInListener)
+
+ const keydown = createEvent('keydown')
+ keydown.key = 'Tab'
+
+ document.dispatchEvent(keydown)
+ outside.focus()
+ })
+
+ it('should wrap focus around backwards on shift-tab', done => {
+ fixtureEl.innerHTML = [
+ '<a href="#" id="outside">outside</a>',
+ '<div id="focustrap" tabindex="-1">',
+ ' <a href="#" id="first">first</a>',
+ ' <a href="#" id="inside">inside</a>',
+ ' <a href="#" id="last">last</a>',
+ '</div>'
+ ].join('')
+
+ const trapElement = fixtureEl.querySelector('div')
+ const focustrap = new FocusTrap({ trapElement })
+ focustrap.activate()
+
+ const first = document.getElementById('first')
+ const inside = document.getElementById('inside')
+ const last = document.getElementById('last')
+ const outside = document.getElementById('outside')
+
+ spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
+ spyOn(last, 'focus').and.callThrough()
+
+ const focusInListener = () => {
+ expect(last.focus).toHaveBeenCalled()
+ last.removeEventListener('focusin', focusInListener)
+ done()
+ }
+
+ last.addEventListener('focusin', focusInListener)
+
+ const keydown = createEvent('keydown')
+ keydown.key = 'Tab'
+ keydown.shiftKey = true
+
+ document.dispatchEvent(keydown)
+ outside.focus()
+ })
+
+ it('should force focus on itself if there is no focusable content', done => {
+ fixtureEl.innerHTML = [
+ '<a href="#" id="outside">outside</a>',
+ '<div id="focustrap" tabindex="-1"></div>'
+ ].join('')
+
+ const trapElement = fixtureEl.querySelector('div')
+ const focustrap = new FocusTrap({ trapElement })
+ focustrap.activate()
+
+ const focusInListener = () => {
+ expect(focustrap._config.trapElement.focus).toHaveBeenCalled()
+ document.removeEventListener('focusin', focusInListener)
+ done()
+ }
+
+ spyOn(focustrap._config.trapElement, 'focus')
+
+ document.addEventListener('focusin', focusInListener)
+
+ const focusInEvent = createEvent('focusin', { bubbles: true })
+ Object.defineProperty(focusInEvent, 'target', {
+ value: document.getElementById('outside')
+ })
+
+ document.dispatchEvent(focusInEvent)
+ })
+ })
+
+ describe('deactivate', () => {
+ it('should flag itself as no longer active', () => {
+ const focustrap = new FocusTrap({ trapElement: fixtureEl })
+ focustrap.activate()
+ expect(focustrap._isActive).toBe(true)
+
+ focustrap.deactivate()
+ expect(focustrap._isActive).toBe(false)
+ })
+
+ it('should remove all event listeners', () => {
+ const focustrap = new FocusTrap({ trapElement: fixtureEl })
+ focustrap.activate()
+
+ spyOn(EventHandler, 'off')
+ focustrap.deactivate()
+
+ expect(EventHandler.off).toHaveBeenCalled()
+ })
+
+ it('doesn\'t try removing event listeners unless it needs to (in case it hasn\'t been activated)', () => {
+ const focustrap = new FocusTrap({ trapElement: fixtureEl })
+
+ spyOn(EventHandler, 'off')
+ focustrap.deactivate()
+
+ expect(EventHandler.off).not.toHaveBeenCalled()
+ })
+ })
+})
diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js
index 9b5d7b70e..38e94dc6b 100644
--- a/js/tests/unit/util/index.spec.js
+++ b/js/tests/unit/util/index.spec.js
@@ -543,8 +543,9 @@ describe('Util', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
-
- expect(Util.reflow(div)).toEqual(0)
+ const spy = spyOnProperty(div, 'offsetHeight')
+ Util.reflow(div)
+ expect(spy).toHaveBeenCalled()
})
})