diff options
Diffstat (limited to 'js/tests/unit/util')
| -rw-r--r-- | js/tests/unit/util/backdrop.spec.js | 441 | ||||
| -rw-r--r-- | js/tests/unit/util/component-functions.spec.js | 40 | ||||
| -rw-r--r-- | js/tests/unit/util/config.spec.js | 166 | ||||
| -rw-r--r-- | js/tests/unit/util/focustrap.spec.js | 266 | ||||
| -rw-r--r-- | js/tests/unit/util/index.spec.js | 499 | ||||
| -rw-r--r-- | js/tests/unit/util/sanitizer.spec.js | 84 | ||||
| -rw-r--r-- | js/tests/unit/util/scrollbar.spec.js | 146 | ||||
| -rw-r--r-- | js/tests/unit/util/swipe.spec.js | 230 | ||||
| -rw-r--r-- | js/tests/unit/util/template-factory.spec.js | 59 |
9 files changed, 1057 insertions, 874 deletions
diff --git a/js/tests/unit/util/backdrop.spec.js b/js/tests/unit/util/backdrop.spec.js index 818ddf221..0faaac6a5 100644 --- a/js/tests/unit/util/backdrop.spec.js +++ b/js/tests/unit/util/backdrop.spec.js @@ -1,6 +1,6 @@ -import Backdrop from '../../../src/util/backdrop' -import { getTransitionDurationFromElement } from '../../../src/util/index' -import { clearFixture, getFixture } from '../../helpers/fixture' +import Backdrop from '../../../src/util/backdrop.js' +import { getTransitionDurationFromElement } from '../../../src/util/index.js' +import { clearFixture, getFixture } from '../../helpers/fixture.js' const CLASS_BACKDROP = '.modal-backdrop' const CLASS_NAME_FADE = 'fade' @@ -23,270 +23,297 @@ describe('Backdrop', () => { }) describe('show', () => { - it('if it is "shown", should append the backdrop html once, on show, and contain "show" class', done => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: false - }) - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) + it('should append the backdrop html once on show and include the "show" class if it is "shown"', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: true, + isAnimated: false + }) + const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - expect(getElements().length).toEqual(0) + expect(getElements()).toHaveSize(0) - instance.show() - instance.show(() => { - expect(getElements().length).toEqual(1) - for (const el of getElements()) { - expect(el.classList.contains(CLASS_NAME_SHOW)).toEqual(true) - } + instance.show() + instance.show(() => { + expect(getElements()).toHaveSize(1) + for (const el of getElements()) { + expect(el).toHaveClass(CLASS_NAME_SHOW) + } - done() + resolve() + }) }) }) - it('if it is not "shown", should not append the backdrop html', done => { - const instance = new Backdrop({ - isVisible: false, - isAnimated: true - }) - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) + it('should not append the backdrop html if it is not "shown"', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: false, + isAnimated: true + }) + const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - expect(getElements().length).toEqual(0) - instance.show(() => { - expect(getElements().length).toEqual(0) - done() + expect(getElements()).toHaveSize(0) + instance.show(() => { + expect(getElements()).toHaveSize(0) + resolve() + }) }) }) - it('if it is "shown" and "animated", should append the backdrop html once, and contain "fade" class', done => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: true - }) - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) + it('should append the backdrop html once and include the "fade" class if it is "shown" and "animated"', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: true, + isAnimated: true + }) + const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - expect(getElements().length).toEqual(0) + expect(getElements()).toHaveSize(0) - instance.show(() => { - expect(getElements().length).toEqual(1) - for (const el of getElements()) { - expect(el.classList.contains(CLASS_NAME_FADE)).toEqual(true) - } + instance.show(() => { + expect(getElements()).toHaveSize(1) + for (const el of getElements()) { + expect(el).toHaveClass(CLASS_NAME_FADE) + } - done() + resolve() + }) }) }) }) describe('hide', () => { - it('should remove the backdrop html', done => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: true - }) + it('should remove the backdrop html', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: true, + isAnimated: true + }) - const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP) + const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP) - expect(getElements().length).toEqual(0) - instance.show(() => { - expect(getElements().length).toEqual(1) - instance.hide(() => { - expect(getElements().length).toEqual(0) - done() + expect(getElements()).toHaveSize(0) + instance.show(() => { + expect(getElements()).toHaveSize(1) + instance.hide(() => { + expect(getElements()).toHaveSize(0) + resolve() + }) }) }) }) - it('should remove "show" class', done => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: true - }) - const elem = instance._getElement() + it('should remove the "show" class', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: true, + isAnimated: true + }) + const elem = instance._getElement() - instance.show() - instance.hide(() => { - expect(elem.classList.contains(CLASS_NAME_SHOW)).toEqual(false) - done() + instance.show() + instance.hide(() => { + expect(elem).not.toHaveClass(CLASS_NAME_SHOW) + resolve() + }) }) }) - it('if it is not "shown", should not try to remove Node on remove method', done => { - const instance = new Backdrop({ - isVisible: false, - isAnimated: true - }) - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - const spy = spyOn(instance, 'dispose').and.callThrough() + it('should not try to remove Node on remove method if it is not "shown"', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: false, + isAnimated: true + }) + const getElements = () => document.querySelectorAll(CLASS_BACKDROP) + const spy = spyOn(instance, 'dispose').and.callThrough() - expect(getElements().length).toEqual(0) - expect(instance._isAppended).toEqual(false) - instance.show(() => { - instance.hide(() => { - expect(getElements().length).toEqual(0) - expect(spy).not.toHaveBeenCalled() - expect(instance._isAppended).toEqual(false) - done() + expect(getElements()).toHaveSize(0) + expect(instance._isAppended).toBeFalse() + instance.show(() => { + instance.hide(() => { + expect(getElements()).toHaveSize(0) + expect(spy).not.toHaveBeenCalled() + expect(instance._isAppended).toBeFalse() + resolve() + }) }) }) }) - it('should not error if the backdrop no longer has a parent', done => { - fixtureEl.innerHTML = '<div id="wrapper"></div>' + it('should not error if the backdrop no longer has a parent', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = '<div id="wrapper"></div>' - const wrapper = fixtureEl.querySelector('#wrapper') - const instance = new Backdrop({ - isVisible: true, - isAnimated: true, - rootElement: wrapper - }) + const wrapper = fixtureEl.querySelector('#wrapper') + const instance = new Backdrop({ + isVisible: true, + isAnimated: true, + rootElement: wrapper + }) - const getElements = () => document.querySelectorAll(CLASS_BACKDROP) + const getElements = () => document.querySelectorAll(CLASS_BACKDROP) - instance.show(() => { - wrapper.remove() - instance.hide(() => { - expect(getElements().length).toEqual(0) - done() + instance.show(() => { + wrapper.remove() + instance.hide(() => { + expect(getElements()).toHaveSize(0) + resolve() + }) }) }) }) }) describe('click callback', () => { - it('it should execute callback on click', done => { - const spy = jasmine.createSpy('spy') + it('should execute callback on click', () => { + return new Promise(resolve => { + const spy = jasmine.createSpy('spy') - const instance = new Backdrop({ - isVisible: true, - isAnimated: false, - clickCallback: () => spy() - }) - const endTest = () => { - setTimeout(() => { - expect(spy).toHaveBeenCalled() - done() - }, 10) - } - - instance.show(() => { - const clickEvent = document.createEvent('MouseEvents') - clickEvent.initEvent('mousedown', true, true) - document.querySelector(CLASS_BACKDROP).dispatchEvent(clickEvent) - endTest() - }) - }) - }) - - describe('animation callbacks', () => { - it('if it is animated, should show and hide backdrop after counting transition duration', done => { - const instance = new Backdrop({ - isVisible: true, - isAnimated: true - }) - const spy2 = jasmine.createSpy('spy2') - - const execDone = () => { - setTimeout(() => { - expect(spy2).toHaveBeenCalledTimes(2) - done() - }, 10) - } - - instance.show(spy2) - instance.hide(() => { - spy2() - execDone() - }) - expect(spy2).not.toHaveBeenCalled() - }) + const instance = new Backdrop({ + isVisible: true, + isAnimated: false, + clickCallback: () => spy() + }) + const endTest = () => { + setTimeout(() => { + expect(spy).toHaveBeenCalled() + resolve() + }, 10) + } - it('if it is not animated, should show and hide backdrop without delay', done => { - const spy = jasmine.createSpy('spy', getTransitionDurationFromElement) - const instance = new Backdrop({ - isVisible: true, - isAnimated: false + instance.show(() => { + const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true }) + document.querySelector(CLASS_BACKDROP).dispatchEvent(clickEvent) + endTest() + }) }) - const spy2 = jasmine.createSpy('spy2') - - instance.show(spy2) - instance.hide(spy2) - - setTimeout(() => { - expect(spy2).toHaveBeenCalled() - expect(spy).not.toHaveBeenCalled() - done() - }, 10) }) - it('if it is not "shown", should not call delay callbacks', done => { - const instance = new Backdrop({ - isVisible: false, - isAnimated: true + describe('animation callbacks', () => { + it('should show and hide backdrop after counting transition duration if it is animated', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: true, + isAnimated: true + }) + const spy2 = jasmine.createSpy('spy2') + + const execDone = () => { + setTimeout(() => { + expect(spy2).toHaveBeenCalledTimes(2) + resolve() + }, 10) + } + + instance.show(spy2) + instance.hide(() => { + spy2() + execDone() + }) + expect(spy2).not.toHaveBeenCalled() + }) }) - const spy = jasmine.createSpy('spy', getTransitionDurationFromElement) - instance.show() - instance.hide(() => { - expect(spy).not.toHaveBeenCalled() - done() - }) - }) - }) - describe('Config', () => { - describe('rootElement initialization', () => { - it('Should be appended on "document.body" by default', done => { - const instance = new Backdrop({ - isVisible: true - }) - const getElement = () => document.querySelector(CLASS_BACKDROP) - instance.show(() => { - expect(getElement().parentElement).toEqual(document.body) - done() + it('should show and hide backdrop without a delay if it is not animated', () => { + return new Promise(resolve => { + const spy = jasmine.createSpy('spy', getTransitionDurationFromElement) + const instance = new Backdrop({ + isVisible: true, + isAnimated: false + }) + const spy2 = jasmine.createSpy('spy2') + + instance.show(spy2) + instance.hide(spy2) + + setTimeout(() => { + expect(spy2).toHaveBeenCalled() + expect(spy).not.toHaveBeenCalled() + resolve() + }, 10) }) }) - it('Should find the rootElement if passed as a string', done => { - const instance = new Backdrop({ - isVisible: true, - rootElement: 'body' - }) - const getElement = () => document.querySelector(CLASS_BACKDROP) - instance.show(() => { - expect(getElement().parentElement).toEqual(document.body) - done() + it('should not call delay callbacks if it is not "shown"', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: false, + isAnimated: true + }) + const spy = jasmine.createSpy('spy', getTransitionDurationFromElement) + + instance.show() + instance.hide(() => { + expect(spy).not.toHaveBeenCalled() + resolve() + }) }) }) + }) - it('Should appended on any element given by the proper config', done => { - fixtureEl.innerHTML = [ - '<div id="wrapper">', - '</div>' - ].join('') + describe('Config', () => { + describe('rootElement initialization', () => { + it('should be appended on "document.body" by default', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: true + }) + const getElement = () => document.querySelector(CLASS_BACKDROP) + instance.show(() => { + expect(getElement().parentElement).toEqual(document.body) + resolve() + }) + }) + }) - const wrapper = fixtureEl.querySelector('#wrapper') - const instance = new Backdrop({ - isVisible: true, - rootElement: wrapper + it('should find the rootElement if passed as a string', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: true, + rootElement: 'body' + }) + const getElement = () => document.querySelector(CLASS_BACKDROP) + instance.show(() => { + expect(getElement().parentElement).toEqual(document.body) + resolve() + }) + }) }) - const getElement = () => document.querySelector(CLASS_BACKDROP) - instance.show(() => { - expect(getElement().parentElement).toEqual(wrapper) - done() + + it('should be appended on any element given by the proper config', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = '<div id="wrapper"></div>' + + const wrapper = fixtureEl.querySelector('#wrapper') + const instance = new Backdrop({ + isVisible: true, + rootElement: wrapper + }) + const getElement = () => document.querySelector(CLASS_BACKDROP) + instance.show(() => { + expect(getElement().parentElement).toEqual(wrapper) + resolve() + }) + }) }) }) - }) - describe('ClassName', () => { - it('Should be able to have different classNames than default', done => { - const instance = new Backdrop({ - isVisible: true, - className: 'foo' - }) - const getElement = () => document.querySelector('.foo') - instance.show(() => { - expect(getElement()).toEqual(instance._getElement()) - instance.dispose() - done() + describe('ClassName', () => { + it('should allow configuring className', () => { + return new Promise(resolve => { + const instance = new Backdrop({ + isVisible: true, + className: 'foo' + }) + const getElement = () => document.querySelector('.foo') + instance.show(() => { + expect(getElement()).toEqual(instance._getElement()) + instance.dispose() + resolve() + }) + }) }) }) }) diff --git a/js/tests/unit/util/component-functions.spec.js b/js/tests/unit/util/component-functions.spec.js index edaedd32e..ce83785e2 100644 --- a/js/tests/unit/util/component-functions.spec.js +++ b/js/tests/unit/util/component-functions.spec.js @@ -1,8 +1,6 @@ -/* Test helpers */ - -import { clearFixture, createEvent, getFixture } from '../../helpers/fixture' -import { enableDismissTrigger } from '../../../src/util/component-functions' -import BaseComponent from '../../../src/base-component' +import BaseComponent from '../../../src/base-component.js' +import { enableDismissTrigger } from '../../../src/util/component-functions.js' +import { clearFixture, createEvent, getFixture } from '../../helpers/fixture.js' class DummyClass2 extends BaseComponent { static get NAME() { @@ -33,12 +31,12 @@ describe('Plugin functions', () => { 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>', + ' <button type="button" data-bs-dismiss="test" data-bs-target="#foo"></button>', '</div>' ].join('') - spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough() - spyOn(DummyClass2.prototype, 'testMethod') + const spyGet = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough() + const spyTest = spyOn(DummyClass2.prototype, 'testMethod') const componentWrapper = fixtureEl.querySelector('#foo') const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]') const event = createEvent('click') @@ -46,19 +44,19 @@ describe('Plugin functions', () => { enableDismissTrigger(DummyClass2, 'testMethod') btnClose.dispatchEvent(event) - expect(DummyClass2.getOrCreateInstance).toHaveBeenCalledWith(componentWrapper) - expect(DummyClass2.prototype.testMethod).toHaveBeenCalled() + expect(spyGet).toHaveBeenCalledWith(componentWrapper) + expect(spyTest).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>', + ' <button type="button" data-bs-dismiss="test"></button>', '</div>' ].join('') - spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough() - spyOn(DummyClass2.prototype, 'hide') + const spyGet = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough() + const spyHide = spyOn(DummyClass2.prototype, 'hide') const componentWrapper = fixtureEl.querySelector('#foo') const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]') const event = createEvent('click') @@ -66,31 +64,31 @@ describe('Plugin functions', () => { enableDismissTrigger(DummyClass2) btnClose.dispatchEvent(event) - expect(DummyClass2.getOrCreateInstance).toHaveBeenCalledWith(componentWrapper) - expect(DummyClass2.prototype.hide).toHaveBeenCalled() + expect(spyGet).toHaveBeenCalledWith(componentWrapper) + expect(spyHide).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>', + ' <button type="button" disabled data-bs-dismiss="test"></button>', '</div>' ].join('') - spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough() + const spy = 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() + expect(spy).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>', + ' <a type="button" data-bs-dismiss="test"></a>', '</div>' ].join('') @@ -98,11 +96,11 @@ describe('Plugin functions', () => { const event = createEvent('click') enableDismissTrigger(DummyClass2) - spyOn(Event.prototype, 'preventDefault').and.callThrough() + const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough() btnClose.dispatchEvent(event) - expect(Event.prototype.preventDefault).toHaveBeenCalled() + expect(spy).toHaveBeenCalled() }) }) }) diff --git a/js/tests/unit/util/config.spec.js b/js/tests/unit/util/config.spec.js new file mode 100644 index 000000000..93987a74a --- /dev/null +++ b/js/tests/unit/util/config.spec.js @@ -0,0 +1,166 @@ +import Config from '../../../src/util/config.js' +import { clearFixture, getFixture } from '../../helpers/fixture.js' + +class DummyConfigClass extends Config { + static get NAME() { + return 'dummy' + } +} + +describe('Config', () => { + let fixtureEl + const name = 'dummy' + + beforeAll(() => { + fixtureEl = getFixture() + }) + + afterEach(() => { + clearFixture() + }) + + describe('NAME', () => { + it('should return plugin NAME', () => { + expect(DummyConfigClass.NAME).toEqual(name) + }) + }) + + describe('DefaultType', () => { + it('should return plugin default type', () => { + expect(DummyConfigClass.DefaultType).toEqual(jasmine.any(Object)) + }) + }) + + describe('Default', () => { + it('should return plugin defaults', () => { + expect(DummyConfigClass.Default).toEqual(jasmine.any(Object)) + }) + }) + + describe('mergeConfigObj', () => { + it('should parse element\'s data attributes and merge it with default config. Element\'s data attributes must excel Defaults', () => { + fixtureEl.innerHTML = '<div id="test" data-bs-test-bool="false" data-bs-test-int="8" data-bs-test-string1="bar"></div>' + + spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({ + testBool: true, + testString: 'foo', + testString1: 'foo', + testInt: 7 + }) + const instance = new DummyConfigClass() + const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test')) + + expect(configResult.testBool).toEqual(false) + expect(configResult.testString).toEqual('foo') + expect(configResult.testString1).toEqual('bar') + expect(configResult.testInt).toEqual(8) + }) + + it('should parse element\'s data attributes and merge it with default config, plug these given during method call. The programmatically given should excel all', () => { + fixtureEl.innerHTML = '<div id="test" data-bs-test-bool="false" data-bs-test-int="8" data-bs-test-string-1="bar"></div>' + + spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({ + testBool: true, + testString: 'foo', + testString1: 'foo', + testInt: 7 + }) + const instance = new DummyConfigClass() + const configResult = instance._mergeConfigObj({ + testString1: 'test', + testInt: 3 + }, fixtureEl.querySelector('#test')) + + expect(configResult.testBool).toEqual(false) + expect(configResult.testString).toEqual('foo') + expect(configResult.testString1).toEqual('test') + expect(configResult.testInt).toEqual(3) + }) + + it('should parse element\'s data attribute `config` and any rest attributes. The programmatically given should excel all. Data attribute `config` should excel only Defaults', () => { + fixtureEl.innerHTML = '<div id="test" data-bs-config=\'{"testBool":false,"testInt":50,"testInt2":100}\' data-bs-test-int="8" data-bs-test-string-1="bar"></div>' + + spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({ + testBool: true, + testString: 'foo', + testString1: 'foo', + testInt: 7, + testInt2: 600 + }) + const instance = new DummyConfigClass() + const configResult = instance._mergeConfigObj({ + testString1: 'test' + }, fixtureEl.querySelector('#test')) + + expect(configResult.testBool).toEqual(false) + expect(configResult.testString).toEqual('foo') + expect(configResult.testString1).toEqual('test') + expect(configResult.testInt).toEqual(8) + expect(configResult.testInt2).toEqual(100) + }) + + it('should omit element\'s data attribute `config` if is not an object', () => { + fixtureEl.innerHTML = '<div id="test" data-bs-config="foo" data-bs-test-int="8"></div>' + + spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({ + testInt: 7, + testInt2: 79 + }) + const instance = new DummyConfigClass() + const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test')) + + expect(configResult.testInt).toEqual(8) + expect(configResult.testInt2).toEqual(79) + }) + }) + + describe('typeCheckConfig', () => { + it('should check type of the config object', () => { + spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({ + toggle: 'boolean', + parent: '(string|element)' + }) + const config = { + toggle: true, + parent: 777 + } + + const obj = new DummyConfigClass() + expect(() => { + obj._typeCheckConfig(config) + }).toThrowError(TypeError, `${obj.constructor.NAME.toUpperCase()}: Option "parent" provided type "number" but expected type "(string|element)".`) + }) + + it('should return null stringified when null is passed', () => { + spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({ + toggle: 'boolean', + parent: '(null|element)' + }) + + const obj = new DummyConfigClass() + const config = { + toggle: true, + parent: null + } + + obj._typeCheckConfig(config) + expect().nothing() + }) + + it('should return undefined stringified when undefined is passed', () => { + spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({ + toggle: 'boolean', + parent: '(undefined|element)' + }) + + const obj = new DummyConfigClass() + const config = { + toggle: true, + parent: undefined + } + + obj._typeCheckConfig(config) + expect().nothing() + }) + }) +}) diff --git a/js/tests/unit/util/focustrap.spec.js b/js/tests/unit/util/focustrap.spec.js index 99bc95fca..0a20017d5 100644 --- a/js/tests/unit/util/focustrap.spec.js +++ b/js/tests/unit/util/focustrap.spec.js @@ -1,7 +1,7 @@ -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' +import EventHandler from '../../../src/dom/event-handler.js' +import SelectorEngine from '../../../src/dom/selector-engine.js' +import FocusTrap from '../../../src/util/focustrap.js' +import { clearFixture, createEvent, getFixture } from '../../helpers/fixture.js' describe('FocusTrap', () => { let fixtureEl @@ -20,12 +20,12 @@ describe('FocusTrap', () => { const trapElement = fixtureEl.querySelector('div') - spyOn(trapElement, 'focus') + const spy = spyOn(trapElement, 'focus') const focustrap = new FocusTrap({ trapElement }) focustrap.activate() - expect(trapElement.focus).toHaveBeenCalled() + expect(spy).toHaveBeenCalled() }) it('if configured not to autofocus, should not autofocus itself', () => { @@ -33,148 +33,156 @@ describe('FocusTrap', () => { const trapElement = fixtureEl.querySelector('div') - spyOn(trapElement, 'focus') + const spy = spyOn(trapElement, 'focus') const focustrap = new FocusTrap({ trapElement, autofocus: false }) focustrap.activate() - expect(trapElement.focus).not.toHaveBeenCalled() + expect(spy).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('') + it('should force focus inside focus trap if it can', () => { + return new Promise(resolve => { + 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 trapElement = fixtureEl.querySelector('div') + const focustrap = new FocusTrap({ trapElement }) + focustrap.activate() - const inside = document.getElementById('inside') + const inside = document.getElementById('inside') - const focusInListener = () => { - expect(inside.focus).toHaveBeenCalled() - document.removeEventListener('focusin', focusInListener) - done() - } + const focusInListener = () => { + expect(spy).toHaveBeenCalled() + document.removeEventListener('focusin', focusInListener) + resolve() + } - spyOn(inside, 'focus') - spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [inside]) + const spy = spyOn(inside, 'focus') + spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [inside]) - document.addEventListener('focusin', focusInListener) + document.addEventListener('focusin', focusInListener) - const focusInEvent = createEvent('focusin', { bubbles: true }) - Object.defineProperty(focusInEvent, 'target', { - value: document.getElementById('outside') - }) + const focusInEvent = createEvent('focusin', { bubbles: true }) + Object.defineProperty(focusInEvent, 'target', { + value: document.getElementById('outside') + }) - document.dispatchEvent(focusInEvent) + 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 forward on tab', () => { + return new Promise(resolve => { + 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]) + const spy = spyOn(first, 'focus').and.callThrough() + + const focusInListener = () => { + expect(spy).toHaveBeenCalled() + first.removeEventListener('focusin', focusInListener) + resolve() + } + + 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 wrap focus around backwards on shift-tab', () => { + return new Promise(resolve => { + 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]) + const spy = spyOn(last, 'focus').and.callThrough() + + const focusInListener = () => { + expect(spy).toHaveBeenCalled() + last.removeEventListener('focusin', focusInListener) + resolve() + } + + 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('') + it('should force focus on itself if there is no focusable content', () => { + return new Promise(resolve => { + 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 trapElement = fixtureEl.querySelector('div') + const focustrap = new FocusTrap({ trapElement }) + focustrap.activate() - const focusInListener = () => { - expect(focustrap._config.trapElement.focus).toHaveBeenCalled() - document.removeEventListener('focusin', focusInListener) - done() - } + const focusInListener = () => { + expect(spy).toHaveBeenCalled() + document.removeEventListener('focusin', focusInListener) + resolve() + } - spyOn(focustrap._config.trapElement, 'focus') + const spy = spyOn(focustrap._config.trapElement, 'focus') - document.addEventListener('focusin', focusInListener) + document.addEventListener('focusin', focusInListener) - const focusInEvent = createEvent('focusin', { bubbles: true }) - Object.defineProperty(focusInEvent, 'target', { - value: document.getElementById('outside') - }) + const focusInEvent = createEvent('focusin', { bubbles: true }) + Object.defineProperty(focusInEvent, 'target', { + value: document.getElementById('outside') + }) - document.dispatchEvent(focusInEvent) + document.dispatchEvent(focusInEvent) + }) }) }) @@ -182,29 +190,29 @@ describe('FocusTrap', () => { it('should flag itself as no longer active', () => { const focustrap = new FocusTrap({ trapElement: fixtureEl }) focustrap.activate() - expect(focustrap._isActive).toBe(true) + expect(focustrap._isActive).toBeTrue() focustrap.deactivate() - expect(focustrap._isActive).toBe(false) + expect(focustrap._isActive).toBeFalse() }) it('should remove all event listeners', () => { const focustrap = new FocusTrap({ trapElement: fixtureEl }) focustrap.activate() - spyOn(EventHandler, 'off') + const spy = spyOn(EventHandler, 'off') focustrap.deactivate() - expect(EventHandler.off).toHaveBeenCalled() + expect(spy).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') + const spy = spyOn(EventHandler, 'off') focustrap.deactivate() - expect(EventHandler.off).not.toHaveBeenCalled() + expect(spy).not.toHaveBeenCalled() }) }) }) diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js index ccfe5e2c2..9e154818f 100644 --- a/js/tests/unit/util/index.spec.js +++ b/js/tests/unit/util/index.spec.js @@ -1,5 +1,6 @@ -import * as Util from '../../../src/util/index' -import { clearFixture, getFixture } from '../../helpers/fixture' +import * as Util from '../../../src/util/index.js' +import { noop } from '../../../src/util/index.js' +import { clearFixture, getFixture } from '../../helpers/fixture.js' describe('Util', () => { let fixtureEl @@ -21,119 +22,6 @@ describe('Util', () => { }) }) - describe('getSelectorFromElement', () => { - it('should get selector from data-bs-target', () => { - fixtureEl.innerHTML = [ - '<div id="test" data-bs-target=".target"></div>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('.target') - }) - - it('should get selector from href if no data-bs-target set', () => { - fixtureEl.innerHTML = [ - '<a id="test" href=".target"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('.target') - }) - - it('should get selector from href if data-bs-target equal to #', () => { - fixtureEl.innerHTML = [ - '<a id="test" data-bs-target="#" href=".target"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('.target') - }) - - it('should return null if a selector from a href is a url without an anchor', () => { - fixtureEl.innerHTML = [ - '<a id="test" data-bs-target="#" href="foo/bar.html"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toBeNull() - }) - - it('should return the anchor if a selector from a href is a url', () => { - fixtureEl.innerHTML = [ - '<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>', - '<div id="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('#target') - }) - - it('should return null if selector not found', () => { - fixtureEl.innerHTML = '<a id="test" href=".target"></a>' - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toBeNull() - }) - - it('should return null if no selector', () => { - fixtureEl.innerHTML = '<div></div>' - - const testEl = fixtureEl.querySelector('div') - - expect(Util.getSelectorFromElement(testEl)).toBeNull() - }) - }) - - describe('getElementFromSelector', () => { - it('should get element from data-bs-target', () => { - fixtureEl.innerHTML = [ - '<div id="test" data-bs-target=".target"></div>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) - }) - - it('should get element from href if no data-bs-target set', () => { - fixtureEl.innerHTML = [ - '<a id="test" href=".target"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) - }) - - it('should return null if element not found', () => { - fixtureEl.innerHTML = '<a id="test" href=".target"></a>' - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getElementFromSelector(testEl)).toBeNull() - }) - - it('should return null if no selector', () => { - fixtureEl.innerHTML = '<div></div>' - - const testEl = fixtureEl.querySelector('div') - - expect(Util.getElementFromSelector(testEl)).toBeNull() - }) - }) - describe('getTransitionDurationFromElement', () => { it('should get transition from element', () => { fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>' @@ -154,34 +42,35 @@ describe('Util', () => { }) describe('triggerTransitionEnd', () => { - it('should trigger transitionend event', done => { - fixtureEl.innerHTML = '<div></div>' + it('should trigger transitionend event', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = '<div></div>' - const el = fixtureEl.querySelector('div') - const spy = spyOn(el, 'dispatchEvent').and.callThrough() + const el = fixtureEl.querySelector('div') + const spy = spyOn(el, 'dispatchEvent').and.callThrough() - el.addEventListener('transitionend', () => { - expect(spy).toHaveBeenCalled() - done() - }) + el.addEventListener('transitionend', () => { + expect(spy).toHaveBeenCalled() + resolve() + }) - Util.triggerTransitionEnd(el) + Util.triggerTransitionEnd(el) + }) }) }) describe('isElement', () => { it('should detect if the parameter is an element or not and return Boolean', () => { - fixtureEl.innerHTML = - [ - '<div id="foo" class="test"></div>', - '<div id="bar" class="test"></div>' - ].join('') + fixtureEl.innerHTML = [ + '<div id="foo" class="test"></div>', + '<div id="bar" class="test"></div>' + ].join('') const el = fixtureEl.querySelector('#foo') - expect(Util.isElement(el)).toEqual(true) - expect(Util.isElement({})).toEqual(false) - expect(Util.isElement(fixtureEl.querySelectorAll('.test'))).toEqual(false) + expect(Util.isElement(el)).toBeTrue() + expect(Util.isElement({})).toBeFalse() + expect(Util.isElement(fixtureEl.querySelectorAll('.test'))).toBeFalse() }) it('should detect jQuery element', () => { @@ -193,17 +82,16 @@ describe('Util', () => { jquery: 'foo' } - expect(Util.isElement(fakejQuery)).toEqual(true) + expect(Util.isElement(fakejQuery)).toBeTrue() }) }) describe('getElement', () => { it('should try to parse element', () => { - fixtureEl.innerHTML = - [ - '<div id="foo" class="test"></div>', - '<div id="bar" class="test"></div>' - ].join('') + fixtureEl.innerHTML = [ + '<div id="foo" class="test"></div>', + '<div id="bar" class="test"></div>' + ].join('') const el = fixtureEl.querySelector('div') @@ -225,61 +113,14 @@ describe('Util', () => { }) }) - describe('typeCheckConfig', () => { - const namePlugin = 'collapse' - - it('should check type of the config object', () => { - const defaultType = { - toggle: 'boolean', - parent: '(string|element)' - } - const config = { - toggle: true, - parent: 777 - } - - expect(() => { - Util.typeCheckConfig(namePlugin, config, defaultType) - }).toThrowError(TypeError, 'COLLAPSE: Option "parent" provided type "number" but expected type "(string|element)".') - }) - - it('should return null stringified when null is passed', () => { - const defaultType = { - toggle: 'boolean', - parent: '(null|element)' - } - const config = { - toggle: true, - parent: null - } - - Util.typeCheckConfig(namePlugin, config, defaultType) - expect().nothing() - }) - - it('should return undefined stringified when undefined is passed', () => { - const defaultType = { - toggle: 'boolean', - parent: '(undefined|element)' - } - const config = { - toggle: true, - parent: undefined - } - - Util.typeCheckConfig(namePlugin, config, defaultType) - expect().nothing() - }) - }) - describe('isVisible', () => { it('should return false if the element is not defined', () => { - expect(Util.isVisible(null)).toEqual(false) - expect(Util.isVisible(undefined)).toEqual(false) + expect(Util.isVisible(null)).toBeFalse() + expect(Util.isVisible(undefined)).toBeFalse() }) it('should return false if the element provided is not a dom element', () => { - expect(Util.isVisible({})).toEqual(false) + expect(Util.isVisible({})).toBeFalse() }) it('should return false if the element is not visible with display none', () => { @@ -287,7 +128,7 @@ describe('Util', () => { const div = fixtureEl.querySelector('div') - expect(Util.isVisible(div)).toEqual(false) + expect(Util.isVisible(div)).toBeFalse() }) it('should return false if the element is not visible with visibility hidden', () => { @@ -295,7 +136,7 @@ describe('Util', () => { const div = fixtureEl.querySelector('div') - expect(Util.isVisible(div)).toEqual(false) + expect(Util.isVisible(div)).toBeFalse() }) it('should return false if an ancestor element is display none', () => { @@ -311,7 +152,7 @@ describe('Util', () => { const div = fixtureEl.querySelector('.content') - expect(Util.isVisible(div)).toEqual(false) + expect(Util.isVisible(div)).toBeFalse() }) it('should return false if an ancestor element is visibility hidden', () => { @@ -327,7 +168,7 @@ describe('Util', () => { const div = fixtureEl.querySelector('.content') - expect(Util.isVisible(div)).toEqual(false) + expect(Util.isVisible(div)).toBeFalse() }) it('should return true if an ancestor element is visibility hidden, but reverted', () => { @@ -343,7 +184,7 @@ describe('Util', () => { const div = fixtureEl.querySelector('.content') - expect(Util.isVisible(div)).toEqual(true) + expect(Util.isVisible(div)).toBeTrue() }) it('should return true if the element is visible', () => { @@ -355,7 +196,7 @@ describe('Util', () => { const div = fixtureEl.querySelector('#element') - expect(Util.isVisible(div)).toEqual(true) + expect(Util.isVisible(div)).toBeTrue() }) it('should return false if the element is hidden, but not via display or visibility', () => { @@ -367,20 +208,56 @@ describe('Util', () => { const div = fixtureEl.querySelector('#element') - expect(Util.isVisible(div)).toEqual(false) + expect(Util.isVisible(div)).toBeFalse() + }) + + it('should return true if its a closed details element', () => { + fixtureEl.innerHTML = '<details id="element"></details>' + + const div = fixtureEl.querySelector('#element') + + expect(Util.isVisible(div)).toBeTrue() + }) + + it('should return true if the element is visible inside an open details element', () => { + fixtureEl.innerHTML = [ + '<details open>', + ' <div id="element"></div>', + '</details>' + ].join('') + + const div = fixtureEl.querySelector('#element') + + expect(Util.isVisible(div)).toBeTrue() + }) + + it('should return true if the element is a visible summary in a closed details element', () => { + fixtureEl.innerHTML = [ + '<details>', + ' <summary id="element-1">', + ' <span id="element-2"></span>', + ' </summary>', + '</details>' + ].join('') + + const element1 = fixtureEl.querySelector('#element-1') + const element2 = fixtureEl.querySelector('#element-2') + + expect(Util.isVisible(element1)).toBeTrue() + expect(Util.isVisible(element2)).toBeTrue() }) }) describe('isDisabled', () => { it('should return true if the element is not defined', () => { - expect(Util.isDisabled(null)).toEqual(true) - expect(Util.isDisabled(undefined)).toEqual(true) - expect(Util.isDisabled()).toEqual(true) + expect(Util.isDisabled(null)).toBeTrue() + expect(Util.isDisabled(undefined)).toBeTrue() + expect(Util.isDisabled()).toBeTrue() }) it('should return true if the element provided is not a dom element', () => { - expect(Util.isDisabled({})).toEqual(true) - expect(Util.isDisabled('test')).toEqual(true) + expect(Util.isDisabled({})).toBeTrue() + expect(Util.isDisabled('test')).toBeTrue() }) it('should return true if the element has disabled attribute', () => { @@ -396,9 +273,9 @@ describe('Util', () => { const div1 = fixtureEl.querySelector('#element1') const div2 = fixtureEl.querySelector('#element2') - expect(Util.isDisabled(div)).toEqual(true) - expect(Util.isDisabled(div1)).toEqual(true) - expect(Util.isDisabled(div2)).toEqual(true) + expect(Util.isDisabled(div)).toBeTrue() + expect(Util.isDisabled(div1)).toBeTrue() + expect(Util.isDisabled(div2)).toBeTrue() }) it('should return false if the element has disabled attribute with "false" value, or doesn\'t have attribute', () => { @@ -412,8 +289,8 @@ describe('Util', () => { const div = fixtureEl.querySelector('#element') const div1 = fixtureEl.querySelector('#element1') - expect(Util.isDisabled(div)).toEqual(false) - expect(Util.isDisabled(div1)).toEqual(false) + expect(Util.isDisabled(div)).toBeFalse() + expect(Util.isDisabled(div1)).toBeFalse() }) it('should return false if the element is not disabled ', () => { @@ -427,15 +304,16 @@ describe('Util', () => { const el = selector => fixtureEl.querySelector(selector) - expect(Util.isDisabled(el('#button'))).toEqual(false) - expect(Util.isDisabled(el('#select'))).toEqual(false) - expect(Util.isDisabled(el('#input'))).toEqual(false) + expect(Util.isDisabled(el('#button'))).toBeFalse() + expect(Util.isDisabled(el('#select'))).toBeFalse() + expect(Util.isDisabled(el('#input'))).toBeFalse() }) + it('should return true if the element has disabled attribute', () => { fixtureEl.innerHTML = [ '<div>', - ' <input id="input" disabled="disabled"/>', - ' <input id="input1" disabled="disabled"/>', + ' <input id="input" disabled="disabled">', + ' <input id="input1" disabled="disabled">', ' <button id="button" disabled="true"></button>', ' <button id="button1" disabled="disabled"></button>', ' <button id="button2" disabled></button>', @@ -446,12 +324,12 @@ describe('Util', () => { const el = selector => fixtureEl.querySelector(selector) - expect(Util.isDisabled(el('#input'))).toEqual(true) - expect(Util.isDisabled(el('#input1'))).toEqual(true) - expect(Util.isDisabled(el('#button'))).toEqual(true) - expect(Util.isDisabled(el('#button1'))).toEqual(true) - expect(Util.isDisabled(el('#button2'))).toEqual(true) - expect(Util.isDisabled(el('#input'))).toEqual(true) + expect(Util.isDisabled(el('#input'))).toBeTrue() + expect(Util.isDisabled(el('#input1'))).toBeTrue() + expect(Util.isDisabled(el('#button'))).toBeTrue() + expect(Util.isDisabled(el('#button1'))).toBeTrue() + expect(Util.isDisabled(el('#button2'))).toBeTrue() + expect(Util.isDisabled(el('#input'))).toBeTrue() }) it('should return true if the element has class "disabled"', () => { @@ -463,19 +341,19 @@ describe('Util', () => { const div = fixtureEl.querySelector('#element') - expect(Util.isDisabled(div)).toEqual(true) + expect(Util.isDisabled(div)).toBeTrue() }) it('should return true if the element has class "disabled" but disabled attribute is false', () => { fixtureEl.innerHTML = [ '<div>', - ' <input id="input" class="disabled" disabled="false"/>', + ' <input id="input" class="disabled" disabled="false">', '</div>' ].join('') const div = fixtureEl.querySelector('#input') - expect(Util.isDisabled(div)).toEqual(true) + expect(Util.isDisabled(div)).toBeTrue() }) }) @@ -493,7 +371,7 @@ describe('Util', () => { spyOn(document.documentElement, 'attachShadow').and.returnValue(null) - expect(Util.findShadowRoot(div)).toEqual(null) + expect(Util.findShadowRoot(div)).toBeNull() }) it('should return null when we do not find a shadow root', () => { @@ -505,7 +383,7 @@ describe('Util', () => { spyOn(document, 'getRootNode').and.returnValue(undefined) - expect(Util.findShadowRoot(document)).toEqual(null) + expect(Util.findShadowRoot(document)).toBeNull() }) it('should return the shadow root when found', () => { @@ -532,7 +410,7 @@ describe('Util', () => { describe('noop', () => { it('should be a function', () => { - expect(typeof Util.noop).toEqual('function') + expect(Util.noop).toEqual(jasmine.any(Function)) }) }) @@ -569,14 +447,14 @@ describe('Util', () => { document.body.setAttribute('data-bs-no-jquery', '') expect(window.jQuery).toEqual(fakejQuery) - expect(Util.getjQuery()).toEqual(null) + expect(Util.getjQuery()).toBeNull() document.body.removeAttribute('data-bs-no-jquery') }) it('should not return jQuery if not present', () => { window.jQuery = undefined - expect(Util.getjQuery()).toEqual(null) + expect(Util.getjQuery()).toBeNull() }) }) @@ -585,7 +463,7 @@ describe('Util', () => { const spy = jasmine.createSpy() const spy2 = jasmine.createSpy() - spyOn(document, 'addEventListener').and.callThrough() + const spyAdd = spyOn(document, 'addEventListener').and.callThrough() spyOnProperty(document, 'readyState').and.returnValue('loading') Util.onDOMContentLoaded(spy) @@ -598,7 +476,7 @@ describe('Util', () => { expect(spy).toHaveBeenCalled() expect(spy2).toHaveBeenCalled() - expect(document.addEventListener).toHaveBeenCalledTimes(1) + expect(spyAdd).toHaveBeenCalledTimes(1) }) it('should execute callback if readyState is not "loading"', () => { @@ -623,14 +501,14 @@ describe('Util', () => { }) it('should define a plugin on the jQuery instance', () => { - const pluginMock = function () {} + const pluginMock = Util.noop pluginMock.NAME = 'test' - pluginMock.jQueryInterface = function () {} + pluginMock.jQueryInterface = Util.noop Util.defineJQueryPlugin(pluginMock) - expect(fakejQuery.fn.test).toBe(pluginMock.jQueryInterface) - expect(fakejQuery.fn.test.Constructor).toBe(pluginMock) - expect(typeof fakejQuery.fn.test.noConflict).toEqual('function') + expect(fakejQuery.fn.test).toEqual(pluginMock.jQueryInterface) + expect(fakejQuery.fn.test.Constructor).toEqual(pluginMock) + expect(fakejQuery.fn.test.noConflict).toEqual(jasmine.any(Function)) }) }) @@ -640,6 +518,25 @@ describe('Util', () => { Util.execute(spy) expect(spy).toHaveBeenCalled() }) + + it('should execute if arg is function & return the result', () => { + const functionFoo = (num1, num2 = 10) => num1 + num2 + const resultFoo = Util.execute(functionFoo, [undefined, 4, 5]) + expect(resultFoo).toBe(9) + + const resultFoo1 = Util.execute(functionFoo, [undefined, 4]) + expect(resultFoo1).toBe(14) + + const functionBar = () => 'foo' + const resultBar = Util.execute(functionBar) + expect(resultBar).toBe('foo') + }) + + it('should not execute if arg is not function & return default argument', () => { + const foo = 'bar' + expect(Util.execute(foo)).toBe('bar') + expect(Util.execute(foo, [], 4)).toBe(4) + }) }) describe('executeAfterTransition', () => { @@ -670,96 +567,104 @@ describe('Util', () => { expect(callbackSpy).toHaveBeenCalled() }) - it('should execute a function after a computed CSS transition duration and there was no transitionend event dispatched', done => { - const el = document.createElement('div') - const callbackSpy = jasmine.createSpy('callback spy') + it('should execute a function after a computed CSS transition duration and there was no transitionend event dispatched', () => { + return new Promise(resolve => { + const el = document.createElement('div') + const callbackSpy = jasmine.createSpy('callback spy') - spyOn(window, 'getComputedStyle').and.returnValue({ - transitionDuration: '0.05s', - transitionDelay: '0s' - }) + spyOn(window, 'getComputedStyle').and.returnValue({ + transitionDuration: '0.05s', + transitionDelay: '0s' + }) - Util.executeAfterTransition(callbackSpy, el) + Util.executeAfterTransition(callbackSpy, el) - setTimeout(() => { - expect(callbackSpy).toHaveBeenCalled() - done() - }, 70) + setTimeout(() => { + expect(callbackSpy).toHaveBeenCalled() + resolve() + }, 70) + }) }) - it('should not execute a function a second time after a computed CSS transition duration and if a transitionend event has already been dispatched', done => { - const el = document.createElement('div') - const callbackSpy = jasmine.createSpy('callback spy') + it('should not execute a function a second time after a computed CSS transition duration and if a transitionend event has already been dispatched', () => { + return new Promise(resolve => { + const el = document.createElement('div') + const callbackSpy = jasmine.createSpy('callback spy') - spyOn(window, 'getComputedStyle').and.returnValue({ - transitionDuration: '0.05s', - transitionDelay: '0s' - }) + spyOn(window, 'getComputedStyle').and.returnValue({ + transitionDuration: '0.05s', + transitionDelay: '0s' + }) - Util.executeAfterTransition(callbackSpy, el) + Util.executeAfterTransition(callbackSpy, el) - setTimeout(() => { - el.dispatchEvent(new TransitionEvent('transitionend')) - }, 50) + setTimeout(() => { + el.dispatchEvent(new TransitionEvent('transitionend')) + }, 50) - setTimeout(() => { - expect(callbackSpy).toHaveBeenCalledTimes(1) - done() - }, 70) + setTimeout(() => { + expect(callbackSpy).toHaveBeenCalledTimes(1) + resolve() + }, 70) + }) }) - it('should not trigger a transitionend event if another transitionend event had already happened', done => { - const el = document.createElement('div') + it('should not trigger a transitionend event if another transitionend event had already happened', () => { + return new Promise(resolve => { + const el = document.createElement('div') - spyOn(window, 'getComputedStyle').and.returnValue({ - transitionDuration: '0.05s', - transitionDelay: '0s' - }) + spyOn(window, 'getComputedStyle').and.returnValue({ + transitionDuration: '0.05s', + transitionDelay: '0s' + }) - Util.executeAfterTransition(() => {}, el) + Util.executeAfterTransition(noop, el) - // simulate a event dispatched by the browser - el.dispatchEvent(new TransitionEvent('transitionend')) + // simulate a event dispatched by the browser + el.dispatchEvent(new TransitionEvent('transitionend')) - const dispatchSpy = spyOn(el, 'dispatchEvent').and.callThrough() + const dispatchSpy = spyOn(el, 'dispatchEvent').and.callThrough() - setTimeout(() => { - // setTimeout should not have triggered another transitionend event. - expect(dispatchSpy).not.toHaveBeenCalled() - done() - }, 70) + setTimeout(() => { + // setTimeout should not have triggered another transitionend event. + expect(dispatchSpy).not.toHaveBeenCalled() + resolve() + }, 70) + }) }) - it('should ignore transitionend events from nested elements', done => { - fixtureEl.innerHTML = [ - '<div class="outer">', - ' <div class="nested"></div>', - '</div>' - ].join('') + it('should ignore transitionend events from nested elements', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = [ + '<div class="outer">', + ' <div class="nested"></div>', + '</div>' + ].join('') - const outer = fixtureEl.querySelector('.outer') - const nested = fixtureEl.querySelector('.nested') - const callbackSpy = jasmine.createSpy('callback spy') + const outer = fixtureEl.querySelector('.outer') + const nested = fixtureEl.querySelector('.nested') + const callbackSpy = jasmine.createSpy('callback spy') - spyOn(window, 'getComputedStyle').and.returnValue({ - transitionDuration: '0.05s', - transitionDelay: '0s' - }) + spyOn(window, 'getComputedStyle').and.returnValue({ + transitionDuration: '0.05s', + transitionDelay: '0s' + }) - Util.executeAfterTransition(callbackSpy, outer) + Util.executeAfterTransition(callbackSpy, outer) - nested.dispatchEvent(new TransitionEvent('transitionend', { - bubbles: true - })) + nested.dispatchEvent(new TransitionEvent('transitionend', { + bubbles: true + })) - setTimeout(() => { - expect(callbackSpy).not.toHaveBeenCalled() - }, 20) + setTimeout(() => { + expect(callbackSpy).not.toHaveBeenCalled() + }, 20) - setTimeout(() => { - expect(callbackSpy).toHaveBeenCalled() - done() - }, 70) + setTimeout(() => { + expect(callbackSpy).toHaveBeenCalled() + resolve() + }, 70) + }) }) }) diff --git a/js/tests/unit/util/sanitizer.spec.js b/js/tests/unit/util/sanitizer.spec.js index 28d624c87..2b21ef2e1 100644 --- a/js/tests/unit/util/sanitizer.spec.js +++ b/js/tests/unit/util/sanitizer.spec.js @@ -1,4 +1,4 @@ -import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer' +import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer.js' describe('Sanitizer', () => { describe('sanitizeHtml', () => { @@ -10,17 +10,75 @@ describe('Sanitizer', () => { expect(result).toEqual(empty) }) - it('should sanitize template by removing tags with XSS', () => { - const template = [ - '<div>', - ' <a href="javascript:alert(7)">Click me</a>', - ' <span>Some content</span>', - '</div>' - ].join('') - - const result = sanitizeHtml(template, DefaultAllowlist, null) + it('should retain tags with valid URLs', () => { + const validUrls = [ + '', + 'http://abc', + 'HTTP://abc', + 'https://abc', + 'HTTPS://abc', + 'ftp://abc', + 'FTP://abc', + 'mailto:[email protected]', + 'MAILTO:[email protected]', + 'tel:123-123-1234', + 'TEL:123-123-1234', + 'sip:[email protected]', + 'SIP:[email protected]', + '#anchor', + '/page1.md', + 'http://JavaScript/my.js', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated. + 'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', + 'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', + 'unknown-scheme:abc' + ] + + for (const url of validUrls) { + const template = [ + '<div>', + ` <a href="${url}">Click me</a>`, + ' <span>Some content</span>', + '</div>' + ].join('') + + const result = sanitizeHtml(template, DefaultAllowlist, null) + + expect(result).toContain(`href="${url}"`) + } + }) - expect(result).not.toContain('href="javascript:alert(7)') + it('should sanitize template by removing tags with XSS', () => { + const invalidUrls = [ + // eslint-disable-next-line no-script-url + 'javascript:alert(7)', + // eslint-disable-next-line no-script-url + 'javascript:evil()', + // eslint-disable-next-line no-script-url + 'JavaScript:abc', + ' javascript:abc', + ' \n Java\n Script:abc', + 'javascript:', + 'javascript:', + 'j avascript:', + 'javascript:', + 'javascript:', + 'jav	ascript:alert();', + 'jav\u0000ascript:alert();' + ] + + for (const url of invalidUrls) { + const template = [ + '<div>', + ` <a href="${url}">Click me</a>`, + ' <span>Some content</span>', + '</div>' + ].join('') + + const result = sanitizeHtml(template, DefaultAllowlist, null) + + expect(result).not.toContain(`href="${url}"`) + } }) it('should sanitize template and work with multiple regex', () => { @@ -84,12 +142,12 @@ describe('Sanitizer', () => { return htmlUnsafe } - spyOn(DOMParser.prototype, 'parseFromString') + const spy = spyOn(DOMParser.prototype, 'parseFromString') const result = sanitizeHtml(template, DefaultAllowlist, mySanitize) expect(result).toEqual(template) - expect(DOMParser.prototype.parseFromString).not.toHaveBeenCalled() + expect(spy).not.toHaveBeenCalled() }) it('should allow multiple sanitation passes of the same template', () => { diff --git a/js/tests/unit/util/scrollbar.spec.js b/js/tests/unit/util/scrollbar.spec.js index 280adb8e5..6dadfcdd1 100644 --- a/js/tests/unit/util/scrollbar.spec.js +++ b/js/tests/unit/util/scrollbar.spec.js @@ -1,13 +1,13 @@ -import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture' -import Manipulator from '../../../src/dom/manipulator' -import ScrollBarHelper from '../../../src/util/scrollbar' +import Manipulator from '../../../src/dom/manipulator.js' +import ScrollBarHelper from '../../../src/util/scrollbar.js' +import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture.js' describe('ScrollBar', () => { let fixtureEl const doc = document.documentElement - const parseInt = arg => Number.parseInt(arg, 10) - const getPaddingX = el => parseInt(window.getComputedStyle(el).paddingRight) - const getMarginX = el => parseInt(window.getComputedStyle(el).marginRight) + const parseIntDecimal = arg => Number.parseInt(arg, 10) + const getPaddingX = el => parseIntDecimal(window.getComputedStyle(el).paddingRight) + const getMarginX = el => parseIntDecimal(window.getComputedStyle(el).marginRight) const getOverFlow = el => el.style.overflow const getPaddingAttr = el => Manipulator.getDataAttribute(el, 'padding-right') const getMarginAttr = el => Manipulator.getDataAttribute(el, 'margin-right') @@ -24,7 +24,9 @@ describe('ScrollBar', () => { } } - const isScrollBarHidden = () => { // IOS devices, Android devices and Browsers on Mac, hide scrollbar by default and appear it, only while scrolling. So the tests for scrollbar would fail + // iOS, Android devices and macOS browsers hide scrollbar by default and show it only while scrolling. + // So the tests for scrollbar would fail + const isScrollBarHidden = () => { const calc = windowCalculations() return calc.htmlClient === calc.htmlOffset && calc.htmlClient === calc.window } @@ -52,28 +54,24 @@ describe('ScrollBar', () => { it('should return true if body is overflowing', () => { document.documentElement.style.overflowY = 'scroll' document.body.style.overflowY = 'scroll' - fixtureEl.innerHTML = [ - '<div style="height: 110vh; width: 100%"></div>' - ].join('') + fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>' const result = new ScrollBarHelper().isOverflowing() if (isScrollBarHidden()) { - expect(result).toEqual(false) + expect(result).toBeFalse() } else { - expect(result).toEqual(true) + expect(result).toBeTrue() } }) it('should return false if body is not overflowing', () => { doc.style.overflowY = 'hidden' document.body.style.overflowY = 'hidden' - fixtureEl.innerHTML = [ - '<div style="height: 110vh; width: 100%"></div>' - ].join('') + fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>' const scrollBar = new ScrollBarHelper() const result = scrollBar.isOverflowing() - expect(result).toEqual(false) + expect(result).toBeFalse() }) }) @@ -81,13 +79,11 @@ describe('ScrollBar', () => { it('should return an integer greater than zero, if body is overflowing', () => { doc.style.overflowY = 'scroll' document.body.style.overflowY = 'scroll' - fixtureEl.innerHTML = [ - '<div style="height: 110vh; width: 100%"></div>' - ].join('') + fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>' const result = new ScrollBarHelper().getWidth() if (isScrollBarHidden()) { - expect(result).toBe(0) + expect(result).toEqual(0) } else { expect(result).toBeGreaterThan(1) } @@ -96,9 +92,7 @@ describe('ScrollBar', () => { it('should return 0 if body is not overflowing', () => { document.documentElement.style.overflowY = 'hidden' document.body.style.overflowY = 'hidden' - fixtureEl.innerHTML = [ - '<div style="height: 110vh; width: 100%"></div>' - ].join('') + fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>' const result = new ScrollBarHelper().getWidth() @@ -107,11 +101,11 @@ describe('ScrollBar', () => { }) describe('hide - reset', () => { - it('should adjust the inline padding of fixed elements which are full-width', done => { + it('should adjust the inline padding of fixed elements which are full-width', () => { fixtureEl.innerHTML = [ - '<div style="height: 110vh; width: 100%">' + - '<div class="fixed-top" id="fixed1" style="padding-right: 0px; width: 100vw"></div>', - '<div class="fixed-top" id="fixed2" style="padding-right: 5px; width: 100vw"></div>', + '<div style="height: 110vh; width: 100%">', + ' <div class="fixed-top" id="fixed1" style="padding-right: 0px; width: 100vw"></div>', + ' <div class="fixed-top" id="fixed2" style="padding-right: 5px; width: 100vw"></div>', '</div>' ].join('') doc.style.overflowY = 'scroll' @@ -128,25 +122,44 @@ describe('ScrollBar', () => { let currentPadding = getPaddingX(fixedEl) let currentPadding2 = getPaddingX(fixedEl2) - expect(getPaddingAttr(fixedEl)).toEqual(`${originalPadding}px`, 'original fixed element padding should be stored in data-bs-padding-right') - expect(getPaddingAttr(fixedEl2)).toEqual(`${originalPadding2}px`, 'original fixed element padding should be stored in data-bs-padding-right') - expect(currentPadding).toEqual(expectedPadding, 'fixed element padding should be adjusted while opening') - expect(currentPadding2).toEqual(expectedPadding2, 'fixed element padding should be adjusted while opening') + expect(getPaddingAttr(fixedEl)).toEqual(`${originalPadding}px`) + expect(getPaddingAttr(fixedEl2)).toEqual(`${originalPadding2}px`) + expect(currentPadding).toEqual(expectedPadding) + expect(currentPadding2).toEqual(expectedPadding2) scrollBar.reset() currentPadding = getPaddingX(fixedEl) currentPadding2 = getPaddingX(fixedEl2) - expect(getPaddingAttr(fixedEl)).toEqual(null, 'data-bs-padding-right should be cleared after closing') - expect(getPaddingAttr(fixedEl2)).toEqual(null, 'data-bs-padding-right should be cleared after closing') - expect(currentPadding).toEqual(originalPadding, 'fixed element padding should be reset after closing') - expect(currentPadding2).toEqual(originalPadding2, 'fixed element padding should be reset after closing') - done() + expect(getPaddingAttr(fixedEl)).toBeNull() + expect(getPaddingAttr(fixedEl2)).toBeNull() + expect(currentPadding).toEqual(originalPadding) + expect(currentPadding2).toEqual(originalPadding2) }) - it('should adjust the inline margin and padding of sticky elements', done => { + it('should remove padding & margin if not existed before adjustment', () => { fixtureEl.innerHTML = [ - '<div style="height: 110vh">' + - '<div class="sticky-top" style="margin-right: 10px; padding-right: 20px; width: 100vw; height: 10px"></div>', + '<div style="height: 110vh; width: 100%">', + ' <div class="fixed" id="fixed" style="width: 100vw;"></div>', + ' <div class="sticky-top" id="sticky" style=" width: 100vw;"></div>', + '</div>' + ].join('') + doc.style.overflowY = 'scroll' + + const fixedEl = fixtureEl.querySelector('#fixed') + const stickyEl = fixtureEl.querySelector('#sticky') + const scrollBar = new ScrollBarHelper() + + scrollBar.hide() + scrollBar.reset() + + expect(fixedEl.getAttribute('style').includes('padding-right')).toBeFalse() + expect(stickyEl.getAttribute('style').includes('margin-right')).toBeFalse() + }) + + it('should adjust the inline margin and padding of sticky elements', () => { + fixtureEl.innerHTML = [ + '<div style="height: 110vh">', + ' <div class="sticky-top" style="margin-right: 10px; padding-right: 20px; width: 100vw; height: 10px"></div>', '</div>' ].join('') doc.style.overflowY = 'scroll' @@ -159,23 +172,20 @@ describe('ScrollBar', () => { const expectedPadding = originalPadding + scrollBar.getWidth() scrollBar.hide() - expect(getMarginAttr(stickyTopEl)).toEqual(`${originalMargin}px`, 'original sticky element margin should be stored in data-bs-margin-right') - expect(getMarginX(stickyTopEl)).toEqual(expectedMargin, 'sticky element margin should be adjusted while opening') - expect(getPaddingAttr(stickyTopEl)).toEqual(`${originalPadding}px`, 'original sticky element margin should be stored in data-bs-margin-right') - expect(getPaddingX(stickyTopEl)).toEqual(expectedPadding, 'sticky element margin should be adjusted while opening') + expect(getMarginAttr(stickyTopEl)).toEqual(`${originalMargin}px`) + expect(getMarginX(stickyTopEl)).toEqual(expectedMargin) + expect(getPaddingAttr(stickyTopEl)).toEqual(`${originalPadding}px`) + expect(getPaddingX(stickyTopEl)).toEqual(expectedPadding) scrollBar.reset() - expect(getMarginAttr(stickyTopEl)).toEqual(null, 'data-bs-margin-right should be cleared after closing') - expect(getMarginX(stickyTopEl)).toEqual(originalMargin, 'sticky element margin should be reset after closing') - expect(getPaddingAttr(stickyTopEl)).toEqual(null, 'data-bs-margin-right should be cleared after closing') - expect(getPaddingX(stickyTopEl)).toEqual(originalPadding, 'sticky element margin should be reset after closing') - done() + expect(getMarginAttr(stickyTopEl)).toBeNull() + expect(getMarginX(stickyTopEl)).toEqual(originalMargin) + expect(getPaddingAttr(stickyTopEl)).toBeNull() + expect(getPaddingX(stickyTopEl)).toEqual(originalPadding) }) it('should not adjust the inline margin and padding of sticky and fixed elements when element do not have full width', () => { - fixtureEl.innerHTML = [ - '<div class="sticky-top" style="margin-right: 0px; padding-right: 0px; width: 50vw"></div>' - ].join('') + fixtureEl.innerHTML = '<div class="sticky-top" style="margin-right: 0px; padding-right: 0px; width: 50vw"></div>' const stickyTopEl = fixtureEl.querySelector('.sticky-top') const originalMargin = getMarginX(stickyTopEl) @@ -187,16 +197,16 @@ describe('ScrollBar', () => { const currentMargin = getMarginX(stickyTopEl) const currentPadding = getPaddingX(stickyTopEl) - expect(currentMargin).toEqual(originalMargin, 'sticky element\'s margin should not be adjusted while opening') - expect(currentPadding).toEqual(originalPadding, 'sticky element\'s padding should not be adjusted while opening') + expect(currentMargin).toEqual(originalMargin) + expect(currentPadding).toEqual(originalPadding) scrollBar.reset() }) it('should not put data-attribute if element doesn\'t have the proper style property, should just remove style property if element didn\'t had one', () => { fixtureEl.innerHTML = [ - '<div style="height: 110vh; width: 100%">' + - '<div class="sticky-top" id="sticky" style="width: 100vw"></div>', + '<div style="height: 110vh; width: 100%">', + ' <div class="sticky-top" id="sticky" style="width: 100vw"></div>', '</div>' ].join('') @@ -232,8 +242,8 @@ describe('ScrollBar', () => { const scrollBarWidth = scrollBar.getWidth() scrollBar.hide() - expect(getPaddingX(document.body)).toEqual(scrollBarWidth, 'body does not have inline padding set') - expect(document.body.style.color).toEqual('red', 'body still has other inline styles set') + expect(getPaddingX(document.body)).toEqual(scrollBarWidth) + expect(document.body.style.color).toEqual('red') scrollBar.reset() }) @@ -243,7 +253,7 @@ describe('ScrollBar', () => { fixtureEl.innerHTML = [ '<style>', ' body {', - ` padding-right: ${styleSheetPadding} }`, + ` padding-right: ${styleSheetPadding}`, ' }', '</style>' ].join('') @@ -253,7 +263,7 @@ describe('ScrollBar', () => { el.style.paddingRight = inlineStylePadding const originalPadding = getPaddingX(el) - expect(originalPadding).toEqual(parseInt(inlineStylePadding)) // Respect only the inline style as it has prevails this of css + expect(originalPadding).toEqual(parseIntDecimal(inlineStylePadding)) // Respect only the inline style as it has prevails this of css const originalOverFlow = 'auto' el.style.overflow = originalOverFlow const scrollBar = new ScrollBarHelper() @@ -264,7 +274,7 @@ describe('ScrollBar', () => { const currentPadding = getPaddingX(el) expect(currentPadding).toEqual(scrollBarWidth + originalPadding) - expect(currentPadding).toEqual(scrollBarWidth + parseInt(inlineStylePadding)) + expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(inlineStylePadding)) expect(getPaddingAttr(el)).toEqual(inlineStylePadding) expect(getOverFlow(el)).toEqual('hidden') expect(getOverFlowAttr(el)).toEqual(originalOverFlow) @@ -273,9 +283,9 @@ describe('ScrollBar', () => { const currentPadding1 = getPaddingX(el) expect(currentPadding1).toEqual(originalPadding) - expect(getPaddingAttr(el)).toEqual(null) + expect(getPaddingAttr(el)).toBeNull() expect(getOverFlow(el)).toEqual(originalOverFlow) - expect(getOverFlowAttr(el)).toEqual(null) + expect(getOverFlowAttr(el)).toBeNull() }) it('should hide scrollbar and reset it to its initial value - respecting css rules', () => { @@ -283,7 +293,7 @@ describe('ScrollBar', () => { fixtureEl.innerHTML = [ '<style>', ' body {', - ` padding-right: ${styleSheetPadding} }`, + ` padding-right: ${styleSheetPadding}`, ' }', '</style>' ].join('') @@ -299,7 +309,7 @@ describe('ScrollBar', () => { const currentPadding = getPaddingX(el) expect(currentPadding).toEqual(scrollBarWidth + originalPadding) - expect(currentPadding).toEqual(scrollBarWidth + parseInt(styleSheetPadding)) + expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(styleSheetPadding)) expect(getPaddingAttr(el)).toBeNull() // We do not have to keep css padding expect(getOverFlow(el)).toEqual('hidden') expect(getOverFlowAttr(el)).toEqual(originalOverFlow) @@ -308,9 +318,9 @@ describe('ScrollBar', () => { const currentPadding1 = getPaddingX(el) expect(currentPadding1).toEqual(originalPadding) - expect(getPaddingAttr(el)).toEqual(null) + expect(getPaddingAttr(el)).toBeNull() expect(getOverFlow(el)).toEqual(originalOverFlow) - expect(getOverFlowAttr(el)).toEqual(null) + expect(getOverFlowAttr(el)).toBeNull() }) it('should not adjust the inline body padding when it does not overflow', () => { @@ -324,7 +334,7 @@ describe('ScrollBar', () => { scrollBar.hide() const currentPadding = getPaddingX(document.body) - expect(currentPadding).toEqual(originalPadding, 'body padding should not be adjusted') + expect(currentPadding).toEqual(originalPadding) scrollBar.reset() }) @@ -344,7 +354,7 @@ describe('ScrollBar', () => { const currentPadding = getPaddingX(document.body) - expect(currentPadding).toEqual(originalPadding, 'body padding should not be adjusted') + expect(currentPadding).toEqual(originalPadding) scrollBar.reset() }) diff --git a/js/tests/unit/util/swipe.spec.js b/js/tests/unit/util/swipe.spec.js index 474e34f65..9252d312b 100644 --- a/js/tests/unit/util/swipe.spec.js +++ b/js/tests/unit/util/swipe.spec.js @@ -1,7 +1,7 @@ -import { clearFixture, getFixture } from '../../helpers/fixture' -import EventHandler from '../../../src/dom/event-handler' -import Swipe from '../../../src/util/swipe' -import { noop } from '../../../src/util' +import EventHandler from '../../../src/dom/event-handler.js' +import { noop } from '../../../src/util/index.js' +import Swipe from '../../../src/util/swipe.js' +import { clearFixture, getFixture } from '../../helpers/fixture.js' describe('Swipe', () => { const { Simulator, PointerEvent } = window @@ -39,17 +39,17 @@ describe('Swipe', () => { fixtureEl = getFixture() const cssStyle = [ '<style>', - ' #fixture .pointer-event {', - ' touch-action: pan-y;', + ' #fixture .pointer-event {', + ' touch-action: pan-y;', ' }', - ' #fixture div {', - ' width: 300px;', - ' height: 300px;', + ' #fixture div {', + ' width: 300px;', + ' height: 300px;', ' }', '</style>' ].join('') - fixtureEl.innerHTML = '<div id="swipeEl"></div>' + cssStyle + fixtureEl.innerHTML = `<div id="swipeEl"></div>${cssStyle}` swipeEl = fixtureEl.querySelector('div') }) @@ -78,74 +78,80 @@ describe('Swipe', () => { }) describe('Config', () => { - it('Test leftCallback', done => { - const spyRight = jasmine.createSpy('spy') - clearPointerEvents() - defineDocumentElementOntouchstart() - // eslint-disable-next-line no-new - new Swipe(swipeEl, { - leftCallback: () => { - expect(spyRight).not.toHaveBeenCalled() - restorePointerEvents() - done() - }, - rightCallback: spyRight - }) - - mockSwipeGesture(swipeEl, { - pos: [300, 10], - deltaX: -300 + it('Test leftCallback', () => { + return new Promise(resolve => { + const spyRight = jasmine.createSpy('spy') + clearPointerEvents() + defineDocumentElementOntouchstart() + // eslint-disable-next-line no-new + new Swipe(swipeEl, { + leftCallback() { + expect(spyRight).not.toHaveBeenCalled() + restorePointerEvents() + resolve() + }, + rightCallback: spyRight + }) + + mockSwipeGesture(swipeEl, { + pos: [300, 10], + deltaX: -300 + }) }) }) - it('Test rightCallback', done => { - const spyLeft = jasmine.createSpy('spy') - clearPointerEvents() - defineDocumentElementOntouchstart() - // eslint-disable-next-line no-new - new Swipe(swipeEl, { - rightCallback: () => { - expect(spyLeft).not.toHaveBeenCalled() - restorePointerEvents() - done() - }, - leftCallback: spyLeft - }) - - mockSwipeGesture(swipeEl, { - pos: [10, 10], - deltaX: 300 + it('Test rightCallback', () => { + return new Promise(resolve => { + const spyLeft = jasmine.createSpy('spy') + clearPointerEvents() + defineDocumentElementOntouchstart() + // eslint-disable-next-line no-new + new Swipe(swipeEl, { + rightCallback() { + expect(spyLeft).not.toHaveBeenCalled() + restorePointerEvents() + resolve() + }, + leftCallback: spyLeft + }) + + mockSwipeGesture(swipeEl, { + pos: [10, 10], + deltaX: 300 + }) }) }) - it('Test endCallback', done => { - clearPointerEvents() - defineDocumentElementOntouchstart() - let isFirstTime = true - - const callback = () => { - if (isFirstTime) { - isFirstTime = false - return - } + it('Test endCallback', () => { + return new Promise(resolve => { + clearPointerEvents() + defineDocumentElementOntouchstart() + let isFirstTime = true - expect().nothing() - restorePointerEvents() - done() - } + const callback = () => { + if (isFirstTime) { + isFirstTime = false + return + } - // eslint-disable-next-line no-new - new Swipe(swipeEl, { - endCallback: callback - }) - mockSwipeGesture(swipeEl, { - pos: [10, 10], - deltaX: 300 - }) + expect().nothing() + restorePointerEvents() + resolve() + } - mockSwipeGesture(swipeEl, { - pos: [300, 10], - deltaX: -300 + // eslint-disable-next-line no-new + new Swipe(swipeEl, { + endCallback: callback + }) + mockSwipeGesture(swipeEl, { + pos: [10, 10], + deltaX: 300 + }) + + mockSwipeGesture(swipeEl, { + pos: [300, 10], + deltaX: -300 + }) }) }) }) @@ -157,7 +163,7 @@ describe('Swipe', () => { deleteDocumentElementOntouchstart() const swipe = new Swipe(swipeEl) - spyOn(swipe, '_handleSwipe') + const spy = spyOn(swipe, '_handleSwipe') mockSwipeGesture(swipeEl, { pos: [300, 10], @@ -167,56 +173,60 @@ describe('Swipe', () => { }) restorePointerEvents() - expect(swipe._handleSwipe).not.toHaveBeenCalled() + expect(spy).not.toHaveBeenCalled() }) - it('should allow swipeRight and call "rightCallback" with pointer events', done => { - if (!supportPointerEvent) { - expect().nothing() - done() - return - } - - const style = '#fixture .pointer-event { touch-action: none !important; }' - fixtureEl.innerHTML += style - - defineDocumentElementOntouchstart() - // eslint-disable-next-line no-new - new Swipe(swipeEl, { - rightCallback: () => { - deleteDocumentElementOntouchstart() + it('should allow swipeRight and call "rightCallback" with pointer events', () => { + return new Promise(resolve => { + if (!supportPointerEvent) { expect().nothing() - done() + resolve() + return } - }) - mockSwipeGesture(swipeEl, { deltaX: 300 }, 'pointer') - }) + const style = '#fixture .pointer-event { touch-action: none !important; }' + fixtureEl.innerHTML += style - it('should allow swipeLeft and call "leftCallback" with pointer events', done => { - if (!supportPointerEvent) { - expect().nothing() - done() - return - } + defineDocumentElementOntouchstart() + // eslint-disable-next-line no-new + new Swipe(swipeEl, { + rightCallback() { + deleteDocumentElementOntouchstart() + expect().nothing() + resolve() + } + }) - const style = '#fixture .pointer-event { touch-action: none !important; }' - fixtureEl.innerHTML += style + mockSwipeGesture(swipeEl, { deltaX: 300 }, 'pointer') + }) + }) - defineDocumentElementOntouchstart() - // eslint-disable-next-line no-new - new Swipe(swipeEl, { - leftCallback: () => { + it('should allow swipeLeft and call "leftCallback" with pointer events', () => { + return new Promise(resolve => { + if (!supportPointerEvent) { expect().nothing() - deleteDocumentElementOntouchstart() - done() + resolve() + return } - }) - mockSwipeGesture(swipeEl, { - pos: [300, 10], - deltaX: -300 - }, 'pointer') + const style = '#fixture .pointer-event { touch-action: none !important; }' + fixtureEl.innerHTML += style + + defineDocumentElementOntouchstart() + // eslint-disable-next-line no-new + new Swipe(swipeEl, { + leftCallback() { + expect().nothing() + deleteDocumentElementOntouchstart() + resolve() + } + }) + + mockSwipeGesture(swipeEl, { + pos: [300, 10], + deltaX: -300 + }, 'pointer') + }) }) }) @@ -266,7 +276,7 @@ describe('Swipe', () => { expect(Swipe.isSupported()).toBeTrue() }) - it('should return "false" if "touchstart" not exists in document element and "navigator.maxTouchPoints" are zero (0)', () => { + it('should return "false" if "touchstart" not exists in document element and "navigator.maxTouchPoints" are zero (0)', () => { Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0) deleteDocumentElementOntouchstart() diff --git a/js/tests/unit/util/template-factory.spec.js b/js/tests/unit/util/template-factory.spec.js index 842c480c2..07f4d91c7 100644 --- a/js/tests/unit/util/template-factory.spec.js +++ b/js/tests/unit/util/template-factory.spec.js @@ -1,5 +1,5 @@ -import { clearFixture, getFixture } from '../../helpers/fixture' -import TemplateFactory from '../../../src/util/template-factory' +import TemplateFactory from '../../../src/util/template-factory.js' +import { clearFixture, getFixture } from '../../helpers/fixture.js' describe('TemplateFactory', () => { let fixtureEl @@ -86,26 +86,26 @@ describe('TemplateFactory', () => { const factory = new TemplateFactory({ extraClass: 'testClass' }) - expect(factory.toHtml().classList.contains('testClass')).toBeTrue() + expect(factory.toHtml()).toHaveClass('testClass') }) it('should add extra classes', () => { const factory = new TemplateFactory({ extraClass: 'testClass testClass2' }) - expect(factory.toHtml().classList.contains('testClass')).toBeTrue() - expect(factory.toHtml().classList.contains('testClass2')).toBeTrue() + expect(factory.toHtml()).toHaveClass('testClass') + expect(factory.toHtml()).toHaveClass('testClass2') }) it('should resolve class if function is given', () => { const factory = new TemplateFactory({ - extraClass: arg => { + extraClass(arg) { expect(arg).toEqual(factory) return 'testClass' } }) - expect(factory.toHtml().classList.contains('testClass')).toBeTrue() + expect(factory.toHtml()).toHaveClass('testClass') }) }) }) @@ -113,11 +113,11 @@ describe('TemplateFactory', () => { describe('Content', () => { it('add simple text content', () => { const template = [ - '<div>' + - '<div class="foo"></div>' + - '<div class="foo2"></div>' + + '<div>', + ' <div class="foo"></div>', + ' <div class="foo2"></div>', '</div>' - ].join(' ') + ].join('') const factory = new TemplateFactory({ template, @@ -128,8 +128,8 @@ describe('TemplateFactory', () => { }) const html = factory.toHtml() - expect(html.querySelector('.foo').textContent).toBe('bar') - expect(html.querySelector('.foo2').textContent).toBe('bar2') + expect(html.querySelector('.foo').textContent).toEqual('bar') + expect(html.querySelector('.foo2').textContent).toEqual('bar2') }) it('should not fill template if selector not exists', () => { @@ -140,7 +140,7 @@ describe('TemplateFactory', () => { content: { '#bar': 'test' } }) - expect(factory.toHtml().outerHTML).toBe('<div id="foo"></div>') + expect(factory.toHtml().outerHTML).toEqual('<div id="foo"></div>') }) it('should remove template selector, if content is null', () => { @@ -151,7 +151,7 @@ describe('TemplateFactory', () => { content: { '#foo': null } }) - expect(factory.toHtml().outerHTML).toBe('<div></div>') + expect(factory.toHtml().outerHTML).toEqual('<div></div>') }) it('should resolve content if is function', () => { @@ -162,7 +162,7 @@ describe('TemplateFactory', () => { content: { '#foo': () => null } }) - expect(factory.toHtml().outerHTML).toBe('<div></div>') + expect(factory.toHtml().outerHTML).toEqual('<div></div>') }) it('if content is element and "config.html=false", should put content\'s textContent', () => { @@ -176,9 +176,9 @@ describe('TemplateFactory', () => { }) const fooEl = factory.toHtml().querySelector('#foo') - expect(fooEl.innerHTML).not.toBe(contentElement.innerHTML) - expect(fooEl.textContent).toBe(contentElement.textContent) - expect(fooEl.textContent).toBe('foobar') + expect(fooEl.innerHTML).not.toEqual(contentElement.innerHTML) + expect(fooEl.textContent).toEqual(contentElement.textContent) + expect(fooEl.textContent).toEqual('foobar') }) it('if content is element and "config.html=true", should put content\'s outerHtml as child', () => { @@ -192,8 +192,8 @@ describe('TemplateFactory', () => { }) const fooEl = factory.toHtml().querySelector('#foo') - expect(fooEl.innerHTML).toBe(contentElement.outerHTML) - expect(fooEl.textContent).toBe(contentElement.textContent) + expect(fooEl.innerHTML).toEqual(contentElement.outerHTML) + expect(fooEl.textContent).toEqual(contentElement.textContent) }) }) @@ -245,14 +245,15 @@ describe('TemplateFactory', () => { expect(factory.hasContent()).toBeFalse() }) }) + describe('changeContent', () => { it('should change Content', () => { const template = [ - '<div>' + - '<div class="foo"></div>' + - '<div class="foo2"></div>' + + '<div>', + ' <div class="foo"></div>', + ' <div class="foo2"></div>', '</div>' - ].join(' ') + ].join('') const factory = new TemplateFactory({ template, @@ -276,11 +277,11 @@ describe('TemplateFactory', () => { it('should change only the given, content', () => { const template = [ - '<div>' + - '<div class="foo"></div>' + - '<div class="foo2"></div>' + + '<div>', + ' <div class="foo"></div>', + ' <div class="foo2"></div>', '</div>' - ].join(' ') + ].join('') const factory = new TemplateFactory({ template, |
