slim-select
Version:
Slim advanced select dropdown
1,191 lines (959 loc) • 35.6 kB
text/typescript
/**
* @jest-environment jsdom
*/
'use strict'
import { describe, expect, test } from '@jest/globals'
import Render, { Callbacks } from './render'
import Settings from './settings'
import Store, { Option } from './store'
import CssClasses from './classes'
describe('render module', () => {
let render: Render
let openMock: jest.Mock
let closeMock: jest.Mock
let addSelectedMock: jest.Mock
let setSelectedMock: jest.Mock
let addOptionMock: jest.Mock
let searchMock: jest.Mock
let afterChangeMock: jest.Mock
let beforeChangeMock: jest.Mock
beforeEach(() => {
const store = new Store('single', [
{
text: 'test0',
value: 'test0'
},
{
text: 'test1',
value: 'test1',
html: '<span>test1</span>'
},
{
text: 'test2',
selected: true
}
])
// default settings
const settings = new Settings()
const classes = new CssClasses()
openMock = jest.fn(() => {})
closeMock = jest.fn(() => {})
addSelectedMock = jest.fn(() => {})
setSelectedMock = jest.fn(() => {})
addOptionMock = jest.fn(() => {})
searchMock = jest.fn(() => {})
afterChangeMock = jest.fn(() => {})
beforeChangeMock = jest.fn(() => true)
// default callbacks
const callbacks = {
open: openMock,
close: closeMock,
addSelected: addSelectedMock,
setSelected: setSelectedMock,
addOption: addOptionMock,
search: searchMock,
afterChange: afterChangeMock,
beforeChange: beforeChangeMock
}
render = new Render(settings, classes, store, callbacks)
})
describe('constructor', () => {
test('default constructor works', () => {
// create a new store with 2 options
const store = new Store('single', [
{ text: 'test1', value: 'test1' },
{ text: 'test2', value: 'test2' }
])
// default settings
const settings = new Settings()
const classes = new CssClasses()
// default callbacks
const callbacks = {
open: () => {},
close: () => {},
addSelected: () => {},
setSelected: () => {},
addOption: () => {},
search: () => {},
beforeChange: () => {
return true
}
} as Callbacks
const render = new Render(settings, classes, store, callbacks)
expect(render).toBeInstanceOf(Render)
expect(render.main.main).toBeInstanceOf(HTMLDivElement)
expect(render.content.search.input).toBeInstanceOf(HTMLInputElement)
})
})
describe('enable', () => {
test('enable removes disabled class from main and enables search input', () => {
// disable stuff directly
render.main.main.classList.add(render.classes.disabled)
render.content.search.input.disabled = true
render.enable()
expect(render.main.main.classList.contains(render.classes.disabled)).toBe(false)
expect(render.content.search.input.disabled).toBe(false)
})
})
describe('disable', () => {
test('disable adds disabled class to main and disables search input', () => {
render.disable()
expect(render.main.main.classList.contains(render.classes.disabled)).toBe(true)
expect(render.content.search.input.disabled).toBe(true)
})
})
describe('open', () => {
test('open sets the correct attributes and CSS classes', () => {
render.open()
expect(render.main.arrow.path.getAttribute('d')).toBe(render.classes.arrowOpen)
expect(render.main.main.classList.contains(render.classes.openBelow)).toBe(true)
expect(render.main.main.classList.contains(render.classes.openAbove)).toBe(false)
expect(render.main.main.getAttribute('aria-expanded')).toBe('true')
expect(render.content.main.classList.contains(render.classes.openBelow)).toBe(true)
expect(render.content.main.classList.contains(render.classes.openAbove)).toBe(false)
})
})
describe('close', () => {
test('close sets the correct attributes and CSS classes', () => {
render.close()
expect(render.main.arrow.path.getAttribute('d')).toBe(render.classes.arrowClose)
expect(render.main.main.classList.contains(render.classes.openBelow)).toBe(false)
expect(render.main.main.classList.contains(render.classes.openAbove)).toBe(false)
expect(render.main.main.getAttribute('aria-expanded')).toBe('false')
expect(render.content.main.classList.contains(render.classes.openBelow)).toBe(false)
expect(render.content.main.classList.contains(render.classes.openAbove)).toBe(false)
})
})
describe('updateClassStyles', () => {
test('existing classes and styles are cleared', () => {
// manually set classes and styles for testing
render.main.main.className = 'test'
render.main.main.style.color = 'red'
render.content.main.className = 'test'
render.content.main.style.color = 'red'
render.updateClassStyles()
expect(render.main.main.classList.contains('test')).toBe(false)
expect(render.main.main.style.color).toBeFalsy()
expect(render.content.main.classList.contains('test')).toBe(false)
expect(render.content.main.style.color).toBeFalsy()
})
test('inline styles are applied to main elements', () => {
render.settings.style = 'color: red'
render.updateClassStyles()
expect(render.main.main.style.color).toBe('red')
expect(render.content.main.style.color).toBe('red')
})
test('classes are applied to main elements', () => {
render.settings.class = ['test0', 'test1', 'test2']
render.updateClassStyles()
expect(render.main.main.classList.contains('test0')).toBe(true)
expect(render.main.main.classList.contains('test1')).toBe(true)
expect(render.main.main.classList.contains('test2')).toBe(true)
expect(render.content.main.classList.contains('test0')).toBe(true)
expect(render.content.main.classList.contains('test1')).toBe(true)
expect(render.content.main.classList.contains('test2')).toBe(true)
})
test('if content position is relative, class is added on content', () => {
render.settings.contentPosition = 'relative'
render.updateClassStyles()
expect(render.content.main.classList.contains('ss-relative')).toBe(true)
})
})
describe('updateAriaAttributes', () => {
test('sets correct aria attributes', () => {
render.updateAriaAttributes()
expect(render.main.main.role).toBe('combobox')
expect(render.main.main.getAttribute('aria-haspopup')).toBe('listbox')
expect(render.main.main.getAttribute('aria-controls')).toBe(render.content.main.id)
expect(render.main.main.getAttribute('aria-expanded')).toBe('false')
expect(render.content.main.getAttribute('role')).toBe('listbox')
})
})
describe('mainDiv', () => {
test('correct HTML element gets created', () => {
const main = render.main.main
expect(main.dataset.id).toBe(render.settings.id)
expect(main.getAttribute('aria-label')).toBe(render.settings.ariaLabel)
expect(main.tabIndex).toBe(0)
expect(main.children).toHaveLength(3)
expect(main.children.item(0)?.className).toBe(render.classes.values)
expect(main.children.item(1)?.classList.contains(render.classes.deselect)).toBe(true)
expect(main.children.item(1)?.classList.contains(render.classes.hide)).toBe(true)
expect(main.children.item(1)?.children).toHaveLength(1)
expect(main.children.item(1)?.children).toHaveLength(1)
expect(main.children.item(1)?.children.item(0)).toBeInstanceOf(SVGElement)
expect(main.children.item(2)?.classList.contains(render.classes.arrow)).toBe(true)
expect(main.children.item(2)?.classList.contains(render.classes.hide)).toBe(false)
expect(main.children.item(2)?.children.item(0)).toBeInstanceOf(SVGElement)
})
test('arrow is hidden when alwaysOpen is set', () => {
render.settings.alwaysOpen = true
const main = render.mainDiv().main
expect(main.children.item(2)?.classList.contains(render.classes.arrow)).toBe(true)
expect(main.children.item(2)?.classList.contains(render.classes.hide)).toBe(true)
expect(main.children.item(2)?.children.item(0)).toBeInstanceOf(SVGElement)
})
test('arrow key events on main element move highlight', () => {
const highlightMock = jest.fn(() => {})
render.highlight = highlightMock
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
expect(openMock).toHaveBeenCalled()
expect(highlightMock).toHaveBeenCalledTimes(1)
expect(highlightMock.mock.calls[0]).toStrictEqual(['up'])
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
expect(openMock).toHaveBeenCalledTimes(2)
expect(highlightMock).toHaveBeenCalledTimes(2)
expect(highlightMock.mock.calls[1]).toStrictEqual(['down'])
})
test('tab and escape key event on main element triggers close callback', () => {
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }))
expect(closeMock).toHaveBeenCalled()
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
expect(closeMock).toHaveBeenCalledTimes(2)
})
test('enter and space key event on main element triggers open callback', () => {
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
expect(openMock).toHaveBeenCalled()
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }))
expect(openMock).toHaveBeenCalledTimes(2)
})
test('click on main event does nothing if element is disabled', () => {
render.settings.disabled = true
render.main.main.dispatchEvent(new MouseEvent('click'))
expect(openMock).not.toHaveBeenCalled()
expect(closeMock).not.toHaveBeenCalled()
})
test('click on main event triggers open if element is closed', () => {
render.main.main.dispatchEvent(new MouseEvent('click'))
expect(openMock).toHaveBeenCalled()
expect(closeMock).not.toHaveBeenCalled()
})
test('click on main event triggers close if element is opened', () => {
render.settings.isOpen = true
render.main.main.dispatchEvent(new MouseEvent('click'))
expect(openMock).not.toHaveBeenCalled()
expect(closeMock).toHaveBeenCalled()
})
test('click on deselect does nothing if element is disabled', () => {
render.settings.disabled = true
const deselectElement: HTMLDivElement = render.main.main.querySelector(
'.' + render.classes.deselect
) as HTMLDivElement
deselectElement.dispatchEvent(new MouseEvent('click'))
expect(afterChangeMock).not.toHaveBeenCalled()
})
test('click on deselect on single select runs callbacks', () => {
const deselectElement: HTMLDivElement = render.main.main.querySelector(
'.' + render.classes.deselect
) as HTMLDivElement
deselectElement.dispatchEvent(new MouseEvent('click'))
expect(setSelectedMock).toHaveBeenCalled()
expect(closeMock).toHaveBeenCalled()
expect(afterChangeMock).toHaveBeenCalled()
})
test('click on deselect on multiple select runs callbacks', () => {
render.settings.isMultiple = true
const deselectAllMock = jest.fn()
render.updateDeselectAll = deselectAllMock
const deselectElement: HTMLDivElement = render.main.main.querySelector(
'.' + render.classes.deselect
) as HTMLDivElement
deselectElement.dispatchEvent(new MouseEvent('click'))
expect(deselectAllMock).toHaveBeenCalled()
expect(setSelectedMock).toHaveBeenCalled()
expect(closeMock).toHaveBeenCalled()
expect(afterChangeMock).toHaveBeenCalled()
})
})
describe('mainFocus', () => {
let focusMock: jest.Mock
beforeEach(() => {
focusMock = jest.fn(() => {})
render.main.main.focus = focusMock
})
test('mainFocus does nothing if the event is click', () => {
render.mainFocus('click')
expect(focusMock).not.toHaveBeenCalled()
})
test('mainFocus triggers focus if the event is not click', () => {
render.mainFocus('keydown')
expect(focusMock).toHaveBeenCalled()
render.mainFocus('keyup')
expect(focusMock).toHaveBeenCalledTimes(2)
render.mainFocus('mouse')
expect(focusMock).toHaveBeenCalledTimes(3)
})
})
describe('placeholder', () => {
test('placeholder uses fallback text if no option is found', () => {
render.settings.placeholderText = 'placeholder text'
const placeholder = render.placeholder()
expect(placeholder.innerHTML).toBe(render.settings.placeholderText)
})
test('placeholder uses option html if option is found', () => {
render.store.setData([
{
text: 'opt text',
html: '<h1>Option HTML</h1>',
placeholder: true
}
])
const placeholder = render.placeholder()
expect(placeholder.innerHTML).toBe('<h1>Option HTML</h1>')
})
test('placeholder uses option text if option is found and no HTML is set', () => {
render.store.setData([
{
text: 'opt text',
placeholder: true
}
])
const placeholder = render.placeholder()
expect(placeholder.innerHTML).toBe('opt text')
})
})
describe('renderValues', () => {
test('single select renders only one value', () => {
render.renderValues()
expect(render.main.values.children).toHaveLength(1)
})
test('single select renders HTML option', () => {
render.store.setData([
{
text: 'opt0'
},
{
text: 'opt1',
html: '<span>opt1</span>',
selected: true
}
])
render.renderValues()
expect(render.main.values.children).toHaveLength(1)
expect(render.main.values.children.item(0)?.innerHTML).toBe('<span>opt1</span>')
})
test('multiple select renders all selected values', () => {
render.settings.isMultiple = true
render.store = new Store('multiple', [
{
text: 'opt0',
value: 'opt0',
selected: true
},
{
text: 'opt1',
value: 'opt1',
html: '<span>opt1</span>',
selected: true
},
{
text: 'opt2'
}
])
render.renderValues()
expect(render.main.values.children).toHaveLength(2)
expect(render.main.values.children.item(0)).toBeInstanceOf(HTMLDivElement)
expect((render.main.values.children.item(0) as HTMLDivElement).textContent).toBe('opt0')
expect(render.main.values.children.item(1)).toBeInstanceOf(HTMLDivElement)
expect((render.main.values.children.item(1) as HTMLDivElement).textContent).toBe('opt1')
})
test('multiple select renders counter element when maxValuesShown is set', () => {
render.settings.isMultiple = true
render.settings.maxValuesShown = 2
render.store = new Store('multiple', [
{
text: 'opt0',
value: 'opt0',
selected: true
},
{
text: 'opt1',
value: 'opt1',
html: '<span>opt1</span>',
selected: true
},
{
text: 'opt2',
value: 'opt2',
selected: true
},
{
text: 'opt4'
}
])
render.renderValues()
expect(render.main.values.children).toHaveLength(1)
expect(render.main.values.children.item(0)?.innerHTML).toBe('3 selected')
})
test('remove old options from values', () => {
render.renderValues()
expect(render.main.values.children).toHaveLength(1)
expect(render.main.values.innerText).toBeFalsy()
render.store.setSelectedBy('value', ['test1'])
render.renderValues()
expect(render.main.values.children).toHaveLength(1)
expect(render.main.values.children.item(0)?.innerHTML).toBe('<span>test1</span>')
})
})
describe('multipleValue', () => {})
describe('contentDiv', () => {})
describe('moveContent', () => {
let contentAboveMock: jest.Mock
let contentBelowMock: jest.Mock
beforeEach(() => {
contentAboveMock = jest.fn()
contentBelowMock = jest.fn()
render.moveContentAbove = contentAboveMock
render.moveContentBelow = contentBelowMock
})
test('content is moved below when position is relative', () => {
render.settings.contentPosition = 'relative'
render.moveContent()
expect(contentAboveMock).not.toHaveBeenCalled()
expect(contentBelowMock).toHaveBeenCalled()
})
test('content is moved below when open position is down', () => {
render.settings.openPosition = 'down'
render.moveContent()
expect(contentAboveMock).not.toHaveBeenCalled()
expect(contentBelowMock).toHaveBeenCalled()
})
test('content is moved above when open position is up', () => {
render.settings.openPosition = 'up'
render.moveContent()
expect(contentAboveMock).toHaveBeenCalled()
expect(contentBelowMock).not.toHaveBeenCalled()
})
test('content is moved above when putContent is up', () => {
render.putContent = jest.fn(() => 'up')
render.moveContent()
expect(contentAboveMock).toHaveBeenCalled()
expect(contentBelowMock).not.toHaveBeenCalled()
})
test('content is moved below when putContent is down', () => {
render.putContent = jest.fn(() => 'down')
render.moveContent()
expect(contentAboveMock).not.toHaveBeenCalled()
expect(contentBelowMock).toHaveBeenCalled()
})
})
describe('searchDiv', () => {
test('search is hidden when showSearch setting is false', () => {
render.settings.showSearch = false
const search = render.searchDiv()
expect(search.main.classList.contains(render.classes.hide)).toBe(true)
})
test('input is debounced', async () => {
const search = render.searchDiv()
search.input.dispatchEvent(new InputEvent('input', { data: 'asdf' }))
// wait for debounce to trigger
await new Promise((r) => setTimeout(r, 101))
expect(searchMock).toHaveBeenCalled()
})
test('arrow keys move highlight', () => {
const search = render.searchDiv()
const highlightMock = jest.fn(() => {})
render.highlight = highlightMock
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
expect(highlightMock).toHaveBeenCalledTimes(1)
expect(highlightMock.mock.calls[0]).toStrictEqual(['up'])
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
expect(highlightMock).toHaveBeenCalledTimes(2)
expect(highlightMock.mock.calls[1]).toStrictEqual(['down'])
})
test('tab triggers close callback', () => {
const search = render.searchDiv()
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }))
expect(closeMock).toHaveBeenCalled()
})
test('escape triggers close callback', () => {
// separate test in case we want to also test the event someday
const search = render.searchDiv()
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
expect(closeMock).toHaveBeenCalled()
})
test("enter and space don't call addable witout ctrl key", () => {
const search = render.searchDiv()
const addableMock = jest.fn((s: string) => ({
text: s,
value: s.toLowerCase()
}))
render.callbacks.addable = addableMock
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
expect(addableMock).not.toHaveBeenCalled()
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }))
expect(addableMock).not.toHaveBeenCalled()
})
test('enter and space call event and does nothing without input value', () => {
const addableMock = jest.fn((s: string) => ({
text: s,
value: s.toLowerCase()
}))
render.callbacks.addable = addableMock
// recreate search because we have added the addable callback
render.content.search = render.searchDiv()
render.content.search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
expect(addableMock).not.toHaveBeenCalled()
})
test('enter and space call addable when defined', () => {
const addableMock = jest.fn((s: string) => ({
text: s,
value: s.toLowerCase()
}))
render.callbacks.addable = addableMock
// recreate search because we have added the addable callback
render.content.search = render.searchDiv()
render.content.search.input.value = 'Search'
render.content.search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
expect(addableMock).toHaveBeenCalledTimes(1)
expect(addableMock.mock.calls[0]).toStrictEqual(['Search'])
})
})
describe('searchFocus', () => {
test('search is focused', () => {
expect(document.activeElement).not.toBe(render.content.search.input)
render.searchFocus()
expect(document.activeElement).toBe(render.content.search.input)
})
})
describe('getOptions', () => {
test('returns all options when called without parameters', () => {
// render all 3 default options and get them back
render.renderOptions(render.store.getDataOptions())
const opts = render.getOptions()
expect(opts).toHaveLength(3)
})
test('filters correctly when filtering out placeholders', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0',
placeholder: true
},
{
text: 'opt1'
},
{
text: 'opt2'
}
])
)
const opts = render.getOptions(true, false, true)
expect(opts).toHaveLength(2)
})
test('filters correctly when filtering out disabled options', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0',
disabled: true
},
{
text: 'opt1'
},
{
text: 'opt2'
}
])
)
const opts = render.getOptions(false, true)
expect(opts).toHaveLength(2)
})
test('filters correctly when filtering out hidden options', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0',
placeholder: true
},
{
text: 'opt1'
},
{
text: 'opt2'
}
])
)
const opts = render.getOptions(false, false, true)
expect(opts).toHaveLength(2)
})
test('filters correctly when filtering out hidden and disabled options', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0',
disabled: true
},
{
text: 'opt1',
placeholder: true
},
{
text: 'opt2'
}
])
)
const opts = render.getOptions(false, true, true)
expect(opts).toHaveLength(1)
})
})
describe('highlight', () => {
test('simply do nothing without breaking when options are empty', () => {
render.renderOptions([])
expect(() => render.highlight('up')).not.toThrowError()
})
test('highlight single option that is not already highlighted', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0'
}
])
)
render.highlight('up')
expect(render.getOptions()[0].classList.contains(render.classes.highlighted)).toBe(true)
})
test('select first option with down when no option is highlighted or selected', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0'
},
{
text: 'opt1'
},
{
text: 'opt2'
}
])
)
render.highlight('down')
expect(render.getOptions()[0].classList.contains(render.classes.highlighted)).toBe(true)
})
test('select last option with up when no option is highlighted or selected', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0'
},
{
text: 'opt1'
},
{
text: 'opt2'
}
])
)
render.highlight('up')
expect(render.getOptions()[2].classList.contains(render.classes.highlighted)).toBe(true)
})
test('highlight next option on down after highlighted option', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0',
class: render.classes.highlighted
},
{
text: 'opt1'
},
{
text: 'opt2'
}
])
)
render.highlight('down')
expect(render.getOptions()[1].classList.contains(render.classes.highlighted)).toBe(true)
})
test('highlight previous option on up before highlighted option', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0'
},
{
text: 'opt1'
},
{
text: 'opt2',
class: render.classes.highlighted
}
])
)
render.highlight('up')
expect(render.getOptions()[1].classList.contains(render.classes.highlighted)).toBe(true)
})
test('highlight next option on down after selected option when no options is highlighted', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0',
selected: true
},
{
text: 'opt1'
},
{
text: 'opt2'
}
])
)
render.highlight('down')
expect(render.getOptions()[1].classList.contains(render.classes.highlighted)).toBe(true)
})
test('skip to last option when using up at the first option', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0',
selected: true
},
{
text: 'opt1'
},
{
text: 'opt2'
}
])
)
render.highlight('up')
expect(render.getOptions()[2].classList.contains(render.classes.highlighted)).toBe(true)
})
test('highlight next option within opt group on down', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0',
selected: true
},
{
label: 'opt group',
options: [
{
text: 'opt1'
}
]
},
{
text: 'opt2'
}
])
)
render.highlight('down')
expect(render.getOptions()[1].classList.contains(render.classes.highlighted)).toBe(true)
})
test('highlight previous option within opt group on up', () => {
render.renderOptions(
render.store.partialToFullData([
{
text: 'opt0'
},
{
label: 'opt group',
options: [
{
text: 'opt1',
selected: true
}
]
},
{
text: 'opt2'
}
])
)
render.highlight('up')
expect(render.getOptions()[0].classList.contains(render.classes.highlighted)).toBe(true)
})
})
describe('listDiv', () => {
test('list div has correct class', () => {
const list = render.listDiv()
expect(list.classList.contains(render.classes.list)).toBe(true)
})
})
describe('renderError', () => {
test('error message is rendered correctly', () => {
expect(render.content.list.children).toHaveLength(0)
render.renderError('test error')
expect(render.content.list.children).toHaveLength(1)
expect(render.content.list.children.item(0)).toBeInstanceOf(HTMLDivElement)
expect(render.content.list.children.item(0)?.className).toBe(render.classes.error)
expect(render.content.list.children.item(0)?.textContent).toBe('test error')
})
test('list is reset on new error', () => {
expect(render.content.list.children).toHaveLength(0)
render.renderError('test error')
expect(render.content.list.children).toHaveLength(1)
render.renderError('error 2')
expect(render.content.list.children).toHaveLength(1)
expect(render.content.list.children.item(0)?.textContent).toBe('error 2')
})
})
describe('renderSearching', () => {
test('search text is rendered correctly', () => {
expect(render.content.list.children).toHaveLength(0)
render.settings.searchingText = 'search'
render.renderSearching()
expect(render.content.list.children).toHaveLength(1)
expect(render.content.list.children.item(0)).toBeInstanceOf(HTMLDivElement)
expect(render.content.list.children.item(0)?.className).toBe(render.classes.searching)
expect(render.content.list.children.item(0)?.textContent).toBe('search')
})
test('list is reset on new search text', () => {
expect(render.content.list.children).toHaveLength(0)
render.settings.searchingText = 'search'
render.renderSearching()
expect(render.content.list.children).toHaveLength(1)
render.settings.searchingText = 'search 2'
render.renderSearching()
expect(render.content.list.children).toHaveLength(1)
expect(render.content.list.children.item(0)?.textContent).toBe('search 2')
})
})
describe('renderOptions', () => {})
describe('option', () => {
test('add inline styles correctly', () => {
const option = render.option(
new Option({
text: 'opt',
style: 'color: red'
})
)
expect(option.style.color).toBe('red')
})
test('add hidden class on option with display false', () => {
const option = render.option(
new Option({
text: 'opt',
display: false
})
)
expect(option.classList.contains(render.classes.hide)).toBe(true)
})
test('add hidden class on selected option when hideSelected setting is true', () => {
render.settings.hideSelected = true
const option = render.option(
new Option({
text: 'opt',
selected: true
})
)
expect(option.classList.contains(render.classes.hide)).toBe(true)
})
test('title attribute is set when showOptionTooltips setting is true', () => {
render.settings.showOptionTooltips = true
const option = render.option(
new Option({
text: 'opt'
})
)
expect(option.getAttribute('title')).toBe('opt')
})
test('text is highlighted correctly with option text', () => {
render.settings.searchHighlight = true
render.content.search.input.value = 'opt'
const option = render.option(
new Option({
text: 'opt 1'
})
)
expect(option.querySelector('mark')).toBeTruthy()
expect(option.querySelector('mark')?.textContent).toBe('opt')
})
test('text is highlighted correctly with option HTML', () => {
render.settings.searchHighlight = true
render.content.search.input.value = 'opt'
const option = render.option(
new Option({
text: 'opt 1',
html: '<h1>opt 1</h1>'
})
)
expect(option.querySelector('mark')).toBeTruthy()
expect(option.querySelector('mark')?.textContent).toBe('opt')
})
test('click does nothing when option is disabled', () => {
const option = render.option(
new Option({
text: 'opt 1',
disabled: true
})
)
option.dispatchEvent(new MouseEvent('click'))
expect(addOptionMock).not.toHaveBeenCalled()
expect(setSelectedMock).not.toHaveBeenCalled()
})
test('click does nothing when max count of selected options is reached', () => {
render.settings.isMultiple = true
render.settings.maxSelected = 1
const option = render.option(
new Option({
text: 'opt 1'
})
)
option.dispatchEvent(new MouseEvent('click'))
expect(addOptionMock).not.toHaveBeenCalled()
expect(setSelectedMock).not.toHaveBeenCalled()
})
test('click does nothing when min count of selected options is reached', () => {
render.settings.isMultiple = true
render.settings.allowDeselect = true
render.settings.minSelected = 1
const option = render.option(
new Option({
text: 'opt 1',
selected: true
})
)
option.dispatchEvent(new MouseEvent('click'))
expect(addOptionMock).not.toHaveBeenCalled()
expect(setSelectedMock).not.toHaveBeenCalled()
})
test('click removes option', () => {
const option = render.option(
new Option({
text: 'new opt 1'
})
)
option.dispatchEvent(new MouseEvent('click'))
expect(addOptionMock).toHaveBeenCalled()
// check that we add the right option
expect(addOptionMock.mock.calls[0][0].text).toBe('new opt 1')
expect(setSelectedMock).toHaveBeenCalled()
})
})
describe('destroy', () => {
test('elements get removed', () => {
expect(render.main.main).toBeInstanceOf(HTMLDivElement)
expect(render.content.main).toBeInstanceOf(HTMLDivElement)
// set some simple IDs, so we can search for the elements in the DOM
render.main.main.id = 'main-id'
render.content.main.id = 'content-id'
render.destroy()
expect(document.getElementById('main-id')).toBeNull()
expect(document.getElementById('#content-id')).toBeNull()
})
})
describe('moveContentAbove', () => {
test('correct classes are set', () => {
render.moveContentAbove()
expect(render.main.main.classList.contains(render.classes.openAbove)).toBe(true)
expect(render.main.main.classList.contains(render.classes.openBelow)).toBe(false)
expect(render.content.main.classList.contains(render.classes.openAbove)).toBe(true)
expect(render.content.main.classList.contains(render.classes.openBelow)).toBe(false)
})
})
describe('moveContentBelow', () => {
test('correct classes are set', () => {
render.moveContentBelow()
expect(render.main.main.classList.contains(render.classes.openAbove)).toBe(false)
expect(render.main.main.classList.contains(render.classes.openBelow)).toBe(true)
expect(render.content.main.classList.contains(render.classes.openAbove)).toBe(false)
expect(render.content.main.classList.contains(render.classes.openBelow)).toBe(true)
})
})
describe('ensureElementInView', () => {})
describe('putContent', () => {})
describe('updateDeselectAll', () => {})
})