diff --git a/js/src/calendar.js b/js/src/calendar.js index db0df4cc0..a3918ece7 100644 --- a/js/src/calendar.js +++ b/js/src/calendar.js @@ -920,11 +920,11 @@ class Calendar extends BaseComponent { return } - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + if (typeof data[config] === 'undefined') { throw new TypeError(`No method named "${config}"`) } - data[config](this) + data[config]() }) } } diff --git a/js/src/range-slider.js b/js/src/range-slider.js index 7f1ca0533..75fa61670 100644 --- a/js/src/range-slider.js +++ b/js/src/range-slider.js @@ -357,7 +357,7 @@ class RangeSlider extends BaseComponent { } } - return null + return '1rem' } _positionTooltip(tooltip, input) { diff --git a/js/tests/unit/calendar.spec.js b/js/tests/unit/calendar.spec.js new file mode 100644 index 000000000..626a993ed --- /dev/null +++ b/js/tests/unit/calendar.spec.js @@ -0,0 +1,301 @@ +/* eslint-env jasmine */ + +import Calendar from '../../src/calendar.js' +import { + getFixture, clearFixture, createEvent, jQueryMock +} from '../helpers/fixture.js' +import EventHandler from '../../src/dom/event-handler.js' + +describe('Calendar', () => { + let fixtureEl + + beforeAll(() => { + fixtureEl = getFixture() + }) + + afterEach(() => { + clearFixture() + }) + + describe('Default', () => { + it('should return plugin default config', () => { + expect(Calendar.Default).toEqual(jasmine.any(Object)) + }) + }) + + describe('DefaultType', () => { + it('should return plugin default type config', () => { + expect(Calendar.DefaultType).toEqual(jasmine.any(Object)) + }) + }) + + describe('NAME', () => { + it('should return plugin NAME', () => { + expect(Calendar.NAME).toEqual('calendar') + }) + }) + + describe('constructor', () => { + it('should create a Calendar instance with default config if no config is provided', () => { + fixtureEl.innerHTML = '
' + + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + + expect(calendar).toBeInstanceOf(Calendar) + expect(calendar._config).toBeDefined() + expect(calendar._element).toEqual(div) + }) + + it('should allow overriding default config', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div, { + locale: 'fr', + calendars: 2, + selectionType: 'month' + }) + + expect(calendar._config.locale).toEqual('fr') + expect(calendar._config.calendars).toEqual(2) + expect(calendar._config.selectionType).toEqual('month') + }) + + it('should set `_view` based on `selectionType`', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div, { + selectionType: 'year' + }) + + expect(calendar._view).toEqual('years') + }) + + it('should properly create the initial markup for days view', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + new Calendar(div) // eslint-disable-line no-new + + // Check if .calendar container is created + expect(div.querySelector('.calendar')).not.toBeNull() + + // Check if .calendar-nav exists + expect(div.querySelector('.calendar-nav')).not.toBeNull() + + // Check if a table with is created (days view) + expect(div.querySelector('.calendar table thead')).not.toBeNull() + expect(div.querySelector('.calendar table tbody')).not.toBeNull() + }) + }) + + describe('update', () => { + it('should clear the calendar HTML and create a new one', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + const oldHtml = div.innerHTML + + calendar.update({ + selectionType: 'month' + }) + + expect(div.innerHTML).not.toEqual(oldHtml) + // Now we should see the months view + expect(div.querySelector('.calendar table thead')).toBeNull() + expect(div.querySelector('.calendar table tbody')).not.toBeNull() + expect(div.querySelector('.month')).not.toBeNull() + }) + + it('should use the new config object after update', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + + calendar.update({ + showWeekNumber: true + }) + + expect(calendar._config.showWeekNumber).toBeTrue() + expect(div.classList).toContain('show-week-numbers') + }) + }) + + describe('keyboard navigation', () => { + it('should select a date on Enter key press', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = '' + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + const showSpy = spyOn(calendar, '_handleCalendarClick').and.callThrough() + + setTimeout(() => { + const dayCell = div.querySelector('.calendar-cell[tabindex="0"]') + expect(dayCell).not.toBeNull() + + const keydownEvent = createEvent('keydown') + keydownEvent.key = 'Enter' + dayCell.dispatchEvent(keydownEvent) + + expect(showSpy).toHaveBeenCalled() + resolve() + }, 10) + }) + }) + }) + + describe('events', () => { + it('should emit `calendarDateChange.coreui.calendar` when the calendar date changes', () => { + fixtureEl.innerHTML = '' + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + const listener = jasmine.createSpy('listener') + + div.addEventListener('calendarDateChange.coreui.calendar', listener) + // For example, go to next month + calendar._modifyCalendarDate(0, 1) + + expect(listener).toHaveBeenCalled() + }) + + it('should emit `startDateChange.coreui.calendar` when the start date is set', () => { + fixtureEl.innerHTML = '' + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + const listener = jasmine.createSpy('listener') + + div.addEventListener('startDateChange.coreui.calendar', listener) + calendar._setStartDate(new Date()) + + expect(listener).toHaveBeenCalled() + }) + + it('should emit `endDateChange.coreui.calendar` when the end date is set', () => { + fixtureEl.innerHTML = '' + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + const listener = jasmine.createSpy('listener') + + div.addEventListener('endDateChange.coreui.calendar', listener) + calendar._setEndDate(new Date()) + + expect(listener).toHaveBeenCalled() + }) + }) + + describe('dispose', () => { + it('should dispose Calendar instance', () => { + fixtureEl.innerHTML = '' + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + const spy = spyOn(EventHandler, 'off').and.callThrough() + + expect(calendar._element).toEqual(div) + calendar.dispose() + + // Typically, you'd set `_element` to null after disposing + expect(calendar._element).toBeNull() + // Should remove all event handlers + expect(spy.calls.count()).toBeGreaterThan(0) + }) + }) + + describe('jQueryInterface', () => { + it('should create a calendar via jQueryInterface', () => { + fixtureEl.innerHTML = '' + const element = fixtureEl.querySelector('[data-coreui-toggle="calendar"]') + + jQueryMock.fn.calendar = Calendar.jQueryInterface + jQueryMock.elements = [element] + jQueryMock.fn.calendar.call(jQueryMock) + + expect(Calendar.getInstance(element)).not.toBeNull() + }) + + it('should throw error on undefined method', () => { + fixtureEl.innerHTML = '' + const element = fixtureEl.querySelector('[data-coreui-toggle="calendar"]') + + jQueryMock.fn.calendar = Calendar.jQueryInterface + jQueryMock.elements = [element] + + expect(() => { + jQueryMock.fn.calendar.call(jQueryMock, 'noMethod') + }).toThrowError(TypeError, 'No method named "noMethod"') + }) + }) + + describe('getInstance', () => { + it('should return calendar instance', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + + expect(Calendar.getInstance(div)).toEqual(calendar) + expect(Calendar.getInstance(div)).toBeInstanceOf(Calendar) + }) + + it('should return null when there is no calendar instance', () => { + fixtureEl.innerHTML = '' + const div = fixtureEl.querySelector('div') + + expect(Calendar.getInstance(div)).toBeNull() + }) + }) + + describe('getOrCreateInstance', () => { + it('should return calendar instance', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div) + + expect(Calendar.getOrCreateInstance(div)).toEqual(calendar) + expect(Calendar.getInstance(div)).toEqual(Calendar.getOrCreateInstance(div, {})) + expect(Calendar.getOrCreateInstance(div)).toBeInstanceOf(Calendar) + }) + + it('should return new instance when there is no calendar instance', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + + expect(Calendar.getInstance(div)).toBeNull() + expect(Calendar.getOrCreateInstance(div)).toBeInstanceOf(Calendar) + }) + + it('should return new instance when there is no calendar instance with given configuration', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + + expect(Calendar.getInstance(div)).toBeNull() + const calendar = Calendar.getOrCreateInstance(div, { + calendars: 3 + }) + expect(calendar).toBeInstanceOf(Calendar) + expect(calendar._config.calendars).toEqual(3) + }) + + it('should return the same instance when exists, ignoring new configuration', () => { + fixtureEl.innerHTML = '' + + const div = fixtureEl.querySelector('div') + const calendar = new Calendar(div, { + calendars: 2 + }) + + const calendar2 = Calendar.getOrCreateInstance(div, { + calendars: 5 + }) + expect(calendar2).toEqual(calendar) + // Original config is still used + expect(calendar2._config.calendars).toEqual(2) + }) + }) +}) diff --git a/js/tests/unit/range-slider.spec.js b/js/tests/unit/range-slider.spec.js index 495e315ac..0af785f50 100644 --- a/js/tests/unit/range-slider.spec.js +++ b/js/tests/unit/range-slider.spec.js @@ -153,37 +153,6 @@ describe('RangeSlider', () => { resolve() }) }) - - it('should handle dragging interactions', () => { - return new Promise(resolve => { - fixtureEl.innerHTML = ` - - ` - - const element = fixtureEl.querySelector('[data-coreui-toggle="range-slider"]') - const rangeSlider = new RangeSlider(element) - - element.addEventListener('change.coreui.range-slider', event => { - const newValue = event.value - expect(newValue).toEqual([50, 75]) - resolve() - }) - - const container = element.querySelector('.range-slider-inputs-container') - - // Simulate mousedown on the first input - const mousedownEvent = createEvent('mousedown', { clientX: 100 }) - container.dispatchEvent(mousedownEvent) - - // Simulate mousemove to update the slider - const mousemoveEvent = createEvent('mousemove', { clientX: 200 }) - document.documentElement.dispatchEvent(mousemoveEvent) - - // Simulate mouseup to end dragging - const mouseupEvent = createEvent('mouseup') - document.documentElement.dispatchEvent(mouseupEvent) - }) - }) }) describe('Configuration Options', () => {