diff --git a/js/src/alert.js b/js/src/alert.js index 8d0838737a..db58d7426b 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -43,8 +43,8 @@ const CLASS_NAME_SHOW = 'show' class Alert extends BaseComponent { // Getters - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -127,6 +127,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DISMISS, Alert.handleDi * add .Alert to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Alert) +defineJQueryPlugin(Alert) export default Alert diff --git a/js/src/base-component.js b/js/src/base-component.js index 588a59d756..eacc8420bc 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -35,7 +35,7 @@ class BaseComponent { dispose() { Data.remove(this._element, this.constructor.DATA_KEY) - EventHandler.off(this._element, `.${this.constructor.DATA_KEY}`) + EventHandler.off(this._element, this.constructor.EVENT_KEY) Object.getOwnPropertyNames(this).forEach(propertyName => { this[propertyName] = null @@ -63,6 +63,18 @@ class BaseComponent { static get VERSION() { return VERSION } + + static get NAME() { + throw new Error('You have to implement the static method "NAME", for each component!') + } + + static get DATA_KEY() { + return `bs.${this.NAME}` + } + + static get EVENT_KEY() { + return `.${this.DATA_KEY}` + } } export default BaseComponent diff --git a/js/src/button.js b/js/src/button.js index 45c691dd43..4932c19552 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -36,8 +36,8 @@ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` class Button extends BaseComponent { // Getters - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -90,6 +90,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => { * add .Button to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Button) +defineJQueryPlugin(Button) export default Button diff --git a/js/src/carousel.js b/js/src/carousel.js index 92733637e9..0e45fed761 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -127,8 +127,8 @@ class Carousel extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -598,6 +598,6 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { * add .Carousel to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Carousel) +defineJQueryPlugin(Carousel) export default Carousel diff --git a/js/src/collapse.js b/js/src/collapse.js index 0d1b91be55..1c8f53ebd6 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -105,8 +105,8 @@ class Collapse extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -390,6 +390,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( * add .Collapse to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Collapse) +defineJQueryPlugin(Collapse) export default Collapse diff --git a/js/src/dropdown.js b/js/src/dropdown.js index f56ab201b7..8f501d8113 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -116,8 +116,8 @@ class Dropdown extends BaseComponent { return DefaultType } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -530,6 +530,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( * add .Dropdown to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Dropdown) +defineJQueryPlugin(Dropdown) export default Dropdown diff --git a/js/src/modal.js b/js/src/modal.js index 44f2a0cbb2..058d4a36b2 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -93,8 +93,8 @@ class Modal extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -441,6 +441,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( * add .Modal to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Modal) +defineJQueryPlugin(Modal) export default Modal diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 8ddb776b1f..65d1e6ba75 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -78,12 +78,12 @@ class Offcanvas extends BaseComponent { // Getters - static get Default() { - return Default + static get NAME() { + return NAME } - static get DATA_KEY() { - return DATA_KEY + static get Default() { + return Default } // Public @@ -271,6 +271,6 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { * ------------------------------------------------------------------------ */ -defineJQueryPlugin(NAME, Offcanvas) +defineJQueryPlugin(Offcanvas) export default Offcanvas diff --git a/js/src/popover.js b/js/src/popover.js index 58b3623280..c105ee2a14 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -76,18 +76,10 @@ class Popover extends Tooltip { return NAME } - static get DATA_KEY() { - return DATA_KEY - } - static get Event() { return Event } - static get EVENT_KEY() { - return EVENT_KEY - } - static get DefaultType() { return DefaultType } @@ -166,6 +158,6 @@ class Popover extends Tooltip { * add .Popover to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Popover) +defineJQueryPlugin(Popover) export default Popover diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 7c59dabcff..d22f489570 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -87,8 +87,8 @@ class ScrollSpy extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -303,6 +303,6 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { * add .ScrollSpy to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, ScrollSpy) +defineJQueryPlugin(ScrollSpy) export default ScrollSpy diff --git a/js/src/tab.js b/js/src/tab.js index 8ee2738113..34107bac03 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -55,8 +55,8 @@ const SELECTOR_DROPDOWN_ACTIVE_CHILD = ':scope > .dropdown-menu .active' class Tab extends BaseComponent { // Getters - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -220,6 +220,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( * add .Tab to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Tab) +defineJQueryPlugin(Tab) export default Tab diff --git a/js/src/toast.js b/js/src/toast.js index cf869cd5a3..e427ea6569 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -81,8 +81,8 @@ class Toast extends BaseComponent { return Default } - static get DATA_KEY() { - return DATA_KEY + static get NAME() { + return NAME } // Public @@ -243,6 +243,6 @@ class Toast extends BaseComponent { * add .Toast to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Toast) +defineJQueryPlugin(Toast) export default Toast diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 7226d31234..632115d72e 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -155,18 +155,10 @@ class Tooltip extends BaseComponent { return NAME } - static get DATA_KEY() { - return DATA_KEY - } - static get Event() { return Event } - static get EVENT_KEY() { - return EVENT_KEY - } - static get DefaultType() { return DefaultType } @@ -774,6 +766,6 @@ class Tooltip extends BaseComponent { * add .Tooltip to jQuery only if jQuery is present */ -defineJQueryPlugin(NAME, Tooltip) +defineJQueryPlugin(Tooltip) export default Tooltip diff --git a/js/src/util/index.js b/js/src/util/index.js index a5144f15e4..0dd6b1d454 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -214,11 +214,12 @@ const onDOMContentLoaded = callback => { const isRTL = () => document.documentElement.dir === 'rtl' -const defineJQueryPlugin = (name, plugin) => { +const defineJQueryPlugin = plugin => { onDOMContentLoaded(() => { const $ = getjQuery() /* istanbul ignore if */ if ($) { + const name = plugin.NAME const JQUERY_NO_CONFLICT = $.fn[name] $.fn[name] = plugin.jQueryInterface $.fn[name].Constructor = plugin diff --git a/js/tests/unit/base-component.spec.js b/js/tests/unit/base-component.spec.js new file mode 100644 index 0000000000..7a849be062 --- /dev/null +++ b/js/tests/unit/base-component.spec.js @@ -0,0 +1,116 @@ +import BaseComponent from '../../src/base-component' +import { clearFixture, getFixture } from '../helpers/fixture' +import EventHandler from '../../src/dom/event-handler' +import { noop } from '../../src/util' + +class DummyClass extends BaseComponent { + constructor(element) { + super(element) + + EventHandler.on(this._element, `click${DummyClass.EVENT_KEY}`, noop) + } + + static get NAME() { + return 'dummy' + } +} + +describe('Base Component', () => { + let fixtureEl + const name = 'dummy' + let element + let instance + const createInstance = () => { + fixtureEl.innerHTML = '
' + element = fixtureEl.querySelector('#foo') + instance = new DummyClass(element) + } + + beforeAll(() => { + fixtureEl = getFixture() + }) + + afterEach(() => { + clearFixture() + }) + + describe('Static Methods', () => { + describe('VERSION', () => { + it('should return version', () => { + expect(typeof DummyClass.VERSION).toEqual('string') + }) + }) + + describe('DATA_KEY', () => { + it('should return plugin data key', () => { + expect(DummyClass.DATA_KEY).toEqual(`bs.${name}`) + }) + }) + + describe('NAME', () => { + it('should return plugin NAME', () => { + expect(DummyClass.NAME).toEqual(name) + }) + }) + + describe('EVENT_KEY', () => { + it('should return plugin event key', () => { + expect(DummyClass.EVENT_KEY).toEqual(`.bs.${name}`) + }) + }) + }) + describe('Public Methods', () => { + describe('constructor', () => { + it('should accept element, either passed as a CSS selector or DOM element', () => { + fixtureEl.innerHTML = [ + '
', + '
' + ].join('') + + const el = fixtureEl.querySelector('#foo') + const elInstance = new DummyClass(el) + const selectorInstance = new DummyClass('#bar') + + expect(elInstance._element).toEqual(el) + expect(selectorInstance._element).toEqual(fixtureEl.querySelector('#bar')) + }) + }) + describe('dispose', () => { + it('should dispose an component', () => { + createInstance() + expect(DummyClass.getInstance(element)).not.toBeNull() + + instance.dispose() + + expect(DummyClass.getInstance(element)).toBeNull() + expect(instance._element).toBeNull() + }) + + it('should de-register element event listeners', () => { + createInstance() + spyOn(EventHandler, 'off') + + instance.dispose() + + expect(EventHandler.off).toHaveBeenCalledWith(element, DummyClass.EVENT_KEY) + }) + }) + + describe('getInstance', () => { + it('should return an instance', () => { + createInstance() + + expect(DummyClass.getInstance(element)).toEqual(instance) + expect(DummyClass.getInstance(element)).toBeInstanceOf(DummyClass) + }) + + it('should return null when there is no instance', () => { + fixtureEl.innerHTML = '
' + + const div = fixtureEl.querySelector('div') + + expect(DummyClass.getInstance(div)).toEqual(null) + }) + }) + }) +}) diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js index 11b6f7fa49..a7c1c28982 100644 --- a/js/tests/unit/util/index.spec.js +++ b/js/tests/unit/util/index.spec.js @@ -560,9 +560,10 @@ describe('Util', () => { it('should define a plugin on the jQuery instance', () => { const pluginMock = function () {} + pluginMock.NAME = 'test' pluginMock.jQueryInterface = function () {} - Util.defineJQueryPlugin('test', pluginMock) + 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')