Skip to content

Commit

Permalink
Merge pull request #557 from mrholek/main
Browse files Browse the repository at this point in the history
Add missing tests
  • Loading branch information
mrholek authored Jan 6, 2025
2 parents 0b4f24f + ae335e3 commit 15e3906
Show file tree
Hide file tree
Showing 6 changed files with 1,562 additions and 2 deletions.
2 changes: 1 addition & 1 deletion js/src/util/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,5 @@ export const isAmPm = locale =>
*/
export const isValidTime = time => {
const d = new Date(`1970-01-01 ${time}`)
return d instanceof Date && d.getTime()
return d instanceof Date && !Number.isNaN(d.getTime())
}
2 changes: 1 addition & 1 deletion js/tests/unit/range-slider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import EventHandler from '../../src/dom/event-handler.js'
import RangeSlider from '../../src/range-slider.js'
import {
clearFixture, createEvent, getFixture, jQueryMock
clearFixture, getFixture, jQueryMock
} from '../helpers/fixture.js'

describe('RangeSlider', () => {
Expand Down
363 changes: 363 additions & 0 deletions js/tests/unit/rating.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
/* eslint-env jasmine */

import Rating from '../../src/rating.js'
import {
getFixture, clearFixture, createEvent, jQueryMock
} from '../helpers/fixture.js'

describe('Rating', () => {
let fixtureEl

beforeAll(() => {
fixtureEl = getFixture()
})

afterEach(() => {
clearFixture()
})

describe('Default', () => {
it('should return plugin default config', () => {
expect(Rating.Default).toEqual(jasmine.any(Object))
})
})

describe('DefaultType', () => {
it('should return plugin default type config', () => {
expect(Rating.DefaultType).toEqual(jasmine.any(Object))
})
})

describe('NAME', () => {
it('should return plugin NAME', () => {
expect(Rating.NAME).toEqual('rating')
})
})

describe('constructor', () => {
it('should create a Rating instance with default config if no config is provided', () => {
fixtureEl.innerHTML = '<div></div>'

const div = fixtureEl.querySelector('div')
const rating = new Rating(div)

expect(rating).toBeInstanceOf(Rating)
expect(rating._config).toBeDefined()
expect(rating._element).toEqual(div)
})

it('should allow overriding default config', () => {
fixtureEl.innerHTML = '<div></div>'

const div = fixtureEl.querySelector('div')
const rating = new Rating(div, {
itemCount: 3,
value: 2,
readOnly: true
})

expect(rating._config.itemCount).toEqual(3)
expect(rating._currentValue).toEqual(2)
expect(rating._config.readOnly).toBeTrue()
})

it('should apply the "disabled" class when config.disabled = true', () => {
fixtureEl.innerHTML = '<div></div>'

const div = fixtureEl.querySelector('div')
// for instance: disabled is set to true
const rating = new Rating(div, { disabled: true })
expect(rating._element.classList).toContain('disabled')
})

it('should create the correct number of rating items', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
new Rating(div, { itemCount: 5 }) // eslint-disable-line no-new

expect(div.querySelectorAll('.rating-item')).toHaveSize(5)
})

it('should set the initial checked input if "value" is provided', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
new Rating(div, { value: 2 }) // eslint-disable-line no-new

const checkedInputs = div.querySelectorAll('.rating-item-input:checked')
expect(checkedInputs).toHaveSize(1)
expect(checkedInputs[0].value).toEqual('2')
})
})

describe('update', () => {
it('should update config and re-render the rating UI', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
const rating = new Rating(div, { itemCount: 3, value: 1 })

const previousHTML = div.innerHTML
rating.update({ itemCount: 5, value: 3 })

expect(div.innerHTML).not.toEqual(previousHTML)
expect(div.querySelectorAll('.rating-item')).toHaveSize(5)
const checkedInput = div.querySelector('.rating-item-input:checked')
expect(checkedInput.value).toEqual('3')
})
})

describe('reset', () => {
it('should reset the rating to the new given value', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
const rating = new Rating(div, { itemCount: 5, value: 4 })

rating.reset(2)
const checkedInput = div.querySelector('.rating-item-input:checked')
expect(checkedInput.value).toEqual('2')
})

it('should reset the rating to null if no argument is provided', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
const rating = new Rating(div, { value: 3 })

rating.reset()
const checkedInput = div.querySelector('.rating-item-input:checked')
expect(checkedInput).toBeNull()
})

it('should emit a "change.coreui.rating" event on reset', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
const rating = new Rating(div, { value: 3 })
const listener = jasmine.createSpy('listener')

div.addEventListener('change.coreui.rating', listener)
rating.reset()
expect(listener).toHaveBeenCalled()
})
})

describe('events', () => {
it('should emit "change.coreui.rating" when a rating input is changed', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
new Rating(div, { itemCount: 3 }) // eslint-disable-line no-new

div.addEventListener('change.coreui.rating', event => {
expect(event.value).toBe('2')
resolve()
})

// Simulate clicking the second radio
const input = div.querySelectorAll('.rating-item-label')[1]
input.click()
})
})

it('should clear the rating if "allowClear" is true and the same value is clicked again', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
// eslint-disable-next-line no-new
new Rating(div, {
value: 2,
allowClear: true,
itemCount: 5
})

// Listen for a new change event
div.addEventListener('change.coreui.rating', event => {
expect(event.value).toBeNull()
resolve()
})

const input2 = div.querySelectorAll('.rating-item-input')[1] // value="2"
input2.click()
})
})

it('should emit "hover.coreui.rating" on mouseenter with the hovered value', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
new Rating(div, { itemCount: 3 }) // eslint-disable-line no-new

const label = div.querySelectorAll('.rating-item-label')[1]
div.addEventListener('hover.coreui.rating', event => {
expect(event.value).toBe('2')
resolve()
})

const mouseover = createEvent('mouseover')
label.dispatchEvent(mouseover)
})
})

it('should remove "active" class from all labels when mouse leaves, unless an input is checked', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
new Rating(div, { itemCount: 3, value: 2 }) // eslint-disable-line no-new

const label = div.querySelectorAll('.rating-item-label')[2]
// first hover:
const mouseenter = createEvent('mouseenter')
label.dispatchEvent(mouseenter)
// all previous labels (0,1,2) active

const mouseleave = createEvent('mouseleave')
label.dispatchEvent(mouseleave)

// Because the rating has a checked input for value="2", items 0 & 1 should remain active
const activeLabels = div.querySelectorAll('.rating-item-label.active')
expect(activeLabels).toHaveSize(2)
})

it('should remove all "active" classes if no input is checked and mouse leaves', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
new Rating(div, { itemCount: 3, value: null }) // eslint-disable-line no-new

const label = div.querySelectorAll('.rating-item-label')[1]
const mouseover = createEvent('mouseover')
label.dispatchEvent(mouseover)

let activeLabels = div.querySelectorAll('.rating-item-label.active')
expect(activeLabels).toHaveSize(2) // items[0] and items[1]

const mouseout = createEvent('mouseout')
label.dispatchEvent(mouseout)

activeLabels = div.querySelectorAll('.rating-item-label.active')
expect(activeLabels).toHaveSize(0)
})
})

describe('readonly & disabled', () => {
it('should not change or hover if readOnly is true', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
new Rating(div, { itemCount: 3, readOnly: true }) // eslint-disable-line no-new

// Attempt to click on an input
const inputs = div.querySelectorAll('.rating-item-input')
inputs[1].click()

const checkedInput = div.querySelector('.rating-item-input:checked')
expect(checkedInput).toBeNull() // Did not change

// Attempt to trigger mouseenter
const label = div.querySelectorAll('.rating-item-label')[1]
const mouseenter = createEvent('mouseenter')
label.dispatchEvent(mouseenter)

const activeLabels = div.querySelectorAll('.rating-item-label.active')
expect(activeLabels).toHaveSize(0)
})

it('should not change or hover if disabled is true', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
new Rating(div, { itemCount: 3, disabled: true }) // eslint-disable-line no-new

// Attempt to click on an input
const inputs = div.querySelectorAll('.rating-item-input')
inputs[1].click()

const checkedInput = div.querySelector('.rating-item-input:checked')
expect(checkedInput).toBeNull() // Did not change
})
})

describe('data-api', () => {
it('should create rating elements on window load event', () => {
fixtureEl.innerHTML = `
<div id="myRating" data-coreui-toggle="rating" data-coreui-value="2" data-coreui-item-count="4"></div>
`
const ratingEl = fixtureEl.querySelector('#myRating')

// Manually trigger the load event
const loadEvent = createEvent('load')
window.dispatchEvent(loadEvent)

const ratingInstance = Rating.getInstance(ratingEl)
expect(ratingInstance).not.toBeNull()
expect(ratingInstance._config.itemCount).toEqual(4)
expect(ratingInstance._currentValue).toEqual(2) // from data attribute
})
})

describe('jQueryInterface', () => {
it('should create a rating via jQueryInterface', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')

jQueryMock.fn.rating = Rating.jQueryInterface
jQueryMock.elements = [div]
jQueryMock.fn.rating.call(jQueryMock)

expect(Rating.getInstance(div)).not.toBeNull()
})

it('should throw error on undefined method', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
jQueryMock.fn.rating = Rating.jQueryInterface
jQueryMock.elements = [div]

expect(() => {
jQueryMock.fn.rating.call(jQueryMock, 'noMethod')
}).toThrowError(TypeError, 'No method named "noMethod"')
})
})

describe('getInstance', () => {
it('should return rating instance', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
const rating = new Rating(div)

expect(Rating.getInstance(div)).toEqual(rating)
expect(Rating.getInstance(div)).toBeInstanceOf(Rating)
})

it('should return null when there is no rating instance', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')

expect(Rating.getInstance(div)).toBeNull()
})
})

describe('getOrCreateInstance', () => {
it('should return rating instance', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
const rating = new Rating(div)

expect(Rating.getOrCreateInstance(div)).toEqual(rating)
expect(Rating.getInstance(div)).toEqual(Rating.getOrCreateInstance(div, {}))
expect(Rating.getOrCreateInstance(div)).toBeInstanceOf(Rating)
})

it('should return new instance when there is no rating instance', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')

expect(Rating.getInstance(div)).toBeNull()
expect(Rating.getOrCreateInstance(div)).toBeInstanceOf(Rating)
})

it('should return the same instance when exists, ignoring new configuration', () => {
fixtureEl.innerHTML = '<div></div>'
const div = fixtureEl.querySelector('div')
const rating = new Rating(div, { itemCount: 3 })

const rating2 = Rating.getOrCreateInstance(div, { itemCount: 5 })
expect(rating2).toEqual(rating)
// config should still show itemCount as 3
expect(rating2._config.itemCount).toEqual(3)
})
})
})
Loading

0 comments on commit 15e3906

Please sign in to comment.