slim-select
Version:
Slim advanced select dropdown
991 lines (855 loc) • 24.5 kB
text/typescript
import { describe, expect, test, beforeAll, beforeEach } from 'vitest'
import Store, { Optgroup, Option } from './store'
describe('store module', () => {
describe('constructor', () => {
test('constructor without data', () => {
let store = new Store('single', [])
expect(store).toBeInstanceOf(Store)
})
test('constructor with single option', () => {
const store = new Store('single', [
{
text: 'test'
}
])
const data = store.getData()
// Make sure data has one item and that it has the correct text
expect(data).toHaveLength(1)
expect(data[0]).toBeInstanceOf(Option)
const option = data[0] as Option
expect(option.text).toBe('test')
expect(option.id).toBeDefined()
expect(option.value).toBeDefined()
expect(option.text).toBeDefined()
expect(option.html).toBeDefined()
expect(option.selected).toBeDefined()
expect(option.display).toBeDefined()
expect(option.disabled).toBeDefined()
expect(option.placeholder).toBeDefined()
expect(option.class).toBeDefined()
expect(option.style).toBeDefined()
expect(option.data).toBeDefined()
expect(option.mandatory).toBeDefined()
})
test('constructor with optgroup', () => {
const store = new Store('single', [
{
label: 'opt group',
options: [
{
text: 'test'
}
]
}
])
const data = store.getData()
expect(data).toHaveLength(1)
expect(data[0]).toBeInstanceOf(Optgroup)
const optGroup = data[0] as Optgroup
expect(optGroup.label).toBe('opt group')
expect(optGroup.options).toHaveLength(1)
expect(optGroup.options?.[0].text).toBe('test')
})
test('constructor with optgroup and option', () => {
const store = new Store('single', [
{
label: 'opt group',
options: [
{
text: 'opt group option'
}
]
},
{
text: 'option'
}
])
const data = store.getData()
expect(data).toHaveLength(2)
expect(data[0]).toBeInstanceOf(Optgroup)
const optGroup = data[0] as Optgroup
expect(optGroup.label).toBe('opt group')
expect(optGroup.options).toHaveLength(1)
expect(optGroup.options?.[0].text).toBe('opt group option')
expect(data[1]).toBeInstanceOf(Option)
expect((data[1] as Option).text).toBe('option')
})
test('constructor with multiple selected on single select only registers first selected', () => {
const store = new Store('single', [
{
text: 'test1'
},
{
text: 'test2',
value: 'test2',
selected: true
},
{
text: 'test3',
selected: true
}
])
// cast to an option array here, so we don't need casts in the comparisons
const data = store.getData() as Array<Option>
expect(data).toHaveLength(3)
expect(data[0].text).toBe('test1')
expect(data[0].selected).toBe(false)
expect(data[1].text).toBe('test2')
expect(data[1].selected).toBe(true)
expect(data[2].text).toBe('test3')
expect(data[2].selected).toBe(false)
})
test('constructor without a selected element on single select selects first option', () => {
const store = new Store('single', [
{
text: 'test1'
},
{
text: 'test2',
value: 'test2'
},
{
text: 'test3'
}
])
// cast to an option array here, so we don't need casts in the comparisons
const data = store.getData() as Array<Option>
expect(data).toHaveLength(3)
expect(data[0].text).toBe('test1')
expect(data[0].selected).toBe(true)
expect(data[1].text).toBe('test2')
expect(data[1].selected).toBe(false)
expect(data[2].text).toBe('test3')
expect(data[2].selected).toBe(false)
})
test('constructor with multiple selected on multiple select registers all selected', () => {
const store = new Store('multiple', [
{
text: 'test1'
},
{
text: 'test2',
value: 'test2',
selected: true
},
{
text: 'test3',
selected: true
}
])
// cast to an option array here, so we don't need casts in the comparisons
const data = store.getData() as Array<Option>
expect(data).toHaveLength(3)
expect(data[0].text).toBe('test1')
expect(data[0].selected).toBe(false)
expect(data[1].text).toBe('test2')
expect(data[1].selected).toBe(true)
expect(data[2].text).toBe('test3')
expect(data[2].selected).toBe(true)
})
test('constructor without a selected element on multiple select does not select anything', () => {
const store = new Store('multiple', [
{
text: 'test1'
},
{
text: 'test2',
value: 'test2'
},
{
text: 'test3'
}
])
// cast to an option array here, so we don't need casts in the comparisons
const data = store.getData() as Array<Option>
expect(data).toHaveLength(3)
data.forEach((option) => {
expect(option.selected).toBe(false)
})
})
})
describe('validateDataArray', () => {
let store: Store
beforeAll(() => {
store = new Store('single', [])
})
test('invalid data returns error', () => {
const invalidData = { test: true } as any
const res = store.validateDataArray(invalidData)
expect(res).toBeInstanceOf(Error)
expect(res?.message).toBe('Data must be an array')
})
test('single invalid data entry returns error', () => {
const invalidData = [{ name: 'this is invalid' }] as any
const res = store.validateDataArray(invalidData)
expect(res).toBeInstanceOf(Error)
expect(res?.message).toBe('Data object must be a valid optgroup or option')
})
test('invalid data and valid option returns error', () => {
const invalidData = [
{
name: 'invalid data'
},
{
text: 'valid option'
}
] as any
const res = store.validateDataArray(invalidData)
expect(res).toBeInstanceOf(Error)
expect(res?.message).toBe('Data object must be a valid optgroup or option')
})
test('valid option and invalid data returns error', () => {
const invalidData = [
{
text: 'valid'
},
{
name: 'this is invalid'
}
] as any
const res = store.validateDataArray(invalidData)
expect(res).toBeInstanceOf(Error)
expect(res?.message).toBe('Data object must be a valid optgroup or option')
})
test('valid opt group with invalid data returns error', () => {
const invalidData = [
{
label: 'valid',
options: [
{
invalid: 'asdf'
}
]
}
] as any
const res = store.validateDataArray(invalidData)
expect(res).toBeInstanceOf(Error)
expect(res?.message).toBe('Option must have a text')
})
test('single valid option validates', () => {
const validData = [
{
text: 'valid option'
}
]
const res = store.validateDataArray(validData)
expect(res).toBeNull()
})
test('multiple valid options validates', () => {
const validData = [
{
text: 'valid option 1'
},
{
text: 'valid option 2'
}
]
const res = store.validateDataArray(validData)
expect(res).toBeNull()
})
test('single valid opt group validates', () => {
const validData = [
{
label: 'valid opt group'
}
]
const res = store.validateDataArray(validData)
expect(res).toBeNull()
})
test('valid opt group with options validates', () => {
const validData = [
{
label: 'valid opt group',
options: [
{
text: 'option 1'
},
{
text: 'option 2'
}
]
}
]
const res = store.validateDataArray(validData)
expect(res).toBeNull()
})
test('valid opt group with options and separate options validates', () => {
const validData = [
{
label: 'valid opt group',
options: [
{
text: 'option 1'
},
{
text: 'option 2'
}
]
},
{
text: 'main option 1'
},
{
text: 'main option 2'
}
]
const res = store.validateDataArray(validData)
expect(res).toBeNull()
})
})
describe('validateOption', () => {
let store: Store
beforeAll(() => {
store = new Store('single', [])
})
test('empty option returns error', () => {
const res = store.validateOption({} as Option)
expect(res).toBeInstanceOf(Error)
expect(res?.message).toBe('Option must have a text')
})
test('invalid data returns error', () => {
const res = store.validateOption({ label: 'text' } as unknown as Option)
expect(res).toBeInstanceOf(Error)
expect(res?.message).toBe('Option must have a text')
})
test('valid data returns null', () => {
const res = store.validateOption({ text: 'text' })
expect(res).toBeNull()
})
})
describe('partialToFullData', () => {
let store: Store
beforeAll(() => {
store = new Store('single', [])
})
test('empty partial returns empty data array', () => {
const res = store.partialToFullData([])
expect(res).toBeInstanceOf(Array)
expect(res).toHaveLength(0)
})
test("invalid data get's ignored", () => {
const res = store.partialToFullData([{ error: 'this is invalid' } as any, { text: 'valid' }])
expect(res).toBeInstanceOf(Array)
expect(res).toHaveLength(1)
expect(res[0]).toBeInstanceOf(Option)
const option = res[0] as Option
expect(option.text).toBe('valid')
})
test('valid data gets filled correctly', () => {
const res = store.partialToFullData([
{
label: 'opt group',
options: [{ text: 'opt 1' }, { text: 'opt 2' }]
},
{
text: 'opt 3'
}
])
expect(res).toHaveLength(2)
expect(res[0]).toBeInstanceOf(Optgroup)
const optGroup = res[0] as Optgroup
expect(optGroup.label).toBe('opt group')
expect(optGroup.options).toHaveLength(2)
expect(optGroup.options[0]).toBeInstanceOf(Option)
expect(optGroup.options[0].text).toBe('opt 1')
expect(optGroup.options[1]).toBeInstanceOf(Option)
expect(optGroup.options[1].text).toBe('opt 2')
})
})
describe('setData', () => {
let store: Store
beforeEach(() => {
store = new Store('single', [
{
text: 'initial option'
},
{
text: 'initial selected option',
selected: true
}
])
})
test('invalid data does override existing data', () => {
store.setData([{ invalid: true } as unknown as Option])
const data = store.getData()
expect(data).toHaveLength(0)
})
test('valid data overrides existing data', () => {
store.setData([
{
text: 'opt 1'
},
{
text: 'opt 2'
},
{
text: 'opt 3'
}
])
const data = store.getData() as Array<Option>
expect(data).toHaveLength(3)
expect(data[0].text).toBe('opt 1')
expect(data[1].text).toBe('opt 2')
expect(data[2].text).toBe('opt 3')
})
test('valid data on single select automatically selects first option', () => {
store.setData([
{
text: 'opt 1'
},
{
text: 'opt 2'
}
])
const data = store.getData() as Array<Option>
expect(data).toHaveLength(2)
expect(data[0].selected).toBe(true)
})
test('valid data on single select selects correct option', () => {
store.setData([
{
text: 'opt 1'
},
{
text: 'opt 2',
selected: true
}
])
const data = store.getData() as Array<Option>
expect(data).toHaveLength(2)
expect(data[1].text).toBe('opt 2')
expect(data[1].selected).toBe(true)
})
})
describe('getData', () => {
test('getData gets all options', () => {
const data = [
{
label: 'group test',
options: [
{
text: 'sub opt 1'
},
{
text: 'sub opt 2'
}
]
},
{
text: 'test1'
},
{
text: 'test2',
value: 'test2'
},
{
text: 'test3'
}
]
const flatData = [
{
text: 'sub opt 1'
},
{
text: 'sub opt 2'
},
{
text: 'test1'
},
{
text: 'test2',
value: 'test2'
},
{
text: 'test3'
}
]
const store = new Store('single', data)
const storedData = store.getData()
storedData.forEach((dataObject, index) => {
if ('label' in dataObject) {
expect(dataObject.label).toBe(data[index].label)
dataObject.options.forEach((subOpt, subIndex) => {
expect(subOpt.text).toBe(data[index].options![subIndex].text)
expect(subOpt.value).toBe(data[index].options![subIndex].text)
})
} else {
expect(dataObject.text).toBe(data[index].text)
expect(dataObject.value).toBe(data[index].text)
}
})
// test flat array directly
const storedDataOptions = store.getDataOptions()
storedDataOptions.forEach((option, index) => {
expect(option.text).toBe(flatData[index].text)
expect(option.value).toBe(flatData[index].text)
})
})
})
describe('getDataOptions', () => {
test('options only store returns same structure', () => {
const store = new Store('single', [
{
text: 'opt 0'
},
{
text: 'opt 1'
}
])
const data = store.getDataOptions()
expect(data).toHaveLength(2)
data.forEach((option, index) => {
expect(option).toBeInstanceOf(Option)
expect(option.text).toBe(`opt ${index}`)
})
})
test('option group gets flattened', () => {
const store = new Store('single', [
{
label: 'opt group',
options: [
{
text: 'opt 0'
},
{
text: 'opt 1'
}
]
},
{
text: 'opt 2'
},
{
text: 'opt 3'
}
])
const data = store.getDataOptions()
expect(data).toHaveLength(4)
data.forEach((option, index) => {
expect(option).toBeInstanceOf(Option)
expect(option.text).toBe(`opt ${index}`)
})
})
})
describe('addOption', () => {
test('append option', () => {
const store = new Store('single', [
{
text: 'test1'
}
])
store.addOption({ text: 'test2' })
const storeData = store.getDataOptions()
expect(storeData).toHaveLength(2)
expect(storeData[0].text).toBe('test1')
expect(storeData[1].text).toBe('test2')
})
})
describe('setSelectedBy', () => {
let store: Store
beforeEach(() => {
store = new Store('single', [
{
text: 'opt 1'
},
{
id: '12345678',
text: 'id opt'
},
{
text: 'opt 2'
},
{
text: 'opt 3'
}
])
})
test('set selected by ID', () => {
store.setSelectedBy('id', ['12345678'])
const data = store.getDataOptions()
expect(data[0].text).toBe('opt 1')
expect(data[0].selected).toBe(false)
expect(data[1].text).toBe('id opt')
expect(data[1].selected).toBe(true)
})
test('set selected by value', () => {
store.setSelectedBy('value', ['opt 3'])
const data = store.getDataOptions()
expect(data[0].text).toBe('opt 1')
expect(data[0].selected).toBe(false)
expect(data[3].text).toBe('opt 3')
expect(data[3].selected).toBe(true)
})
test('set selected to empty string selects first option', () => {
const store = new Store('single', [
{
text: 'all',
value: ''
},
{
text: 'Value 1',
value: '1',
selected: true
}
])
store.setSelectedBy('value', [''])
const data = store.getDataOptions()
expect(data[0].text).toBe('all')
expect(data[0].selected).toBe(true)
expect(data[1].selected).toBe(false)
})
test('set multiple selected by value on single select only selects the first element', () => {
store.setSelectedBy('value', ['opt 2', 'opt 3'])
const data = store.getDataOptions()
expect(data[2].selected).toBe(true)
expect(data[3].selected).toBe(false)
})
})
describe('getSelected', () => {
test('get correct value when one option is selected', () => {
const store = new Store('single', [
{
text: 'opt 0'
},
{
text: 'opt 1'
}
])
const selected = store.getSelectedValues()
expect(selected).toHaveLength(1)
expect(selected[0]).toBe('opt 0')
})
test('get correct value when two options is selected', () => {
const store = new Store('multiple', [
{
text: 'opt 0'
},
{
text: 'opt 1',
selected: true
},
{
text: 'opt 2',
selected: true
}
])
const selected = store.getSelectedValues()
expect(selected).toHaveLength(2)
expect(selected[0]).toBe('opt 1')
expect(selected[1]).toBe('opt 2')
})
test('get correct order when using reorder options', () => {
const store = new Store('multiple', [
{
text: 'opt 0'
},
{
text: 'opt 1'
},
{
text: 'opt 2'
}
])
store.setSelectedBy('value', ['opt 2', 'opt 0'])
let selected = store.getSelectedOptions()
selected = store.selectedOrderOptions(selected)
expect(selected.map((opt) => opt.text)).toEqual(['opt 2', 'opt 0'])
})
})
describe('getSelectedOptions', () => {
test('get correct value when one option is selected', () => {
const store = new Store('single', [
{
text: 'opt 0'
},
{
text: 'opt 1'
}
])
const selected = store.getSelectedOptions()
expect(selected).toHaveLength(1)
expect(selected[0].text).toBe('opt 0')
})
test('get correct value when two options is selected', () => {
const store = new Store('multiple', [
{
text: 'opt 0'
},
{
text: 'opt 1',
selected: true
},
{
text: 'opt 2',
selected: true
}
])
const selected = store.getSelectedOptions()
expect(selected).toHaveLength(2)
expect(selected[0].text).toBe('opt 1')
expect(selected[1].text).toBe('opt 2')
})
})
describe('getOptionByID', () => {
let store: Store
beforeAll(() => {
store = new Store('single', [
{
text: 'opt 1'
},
{
id: '12345678',
text: 'id opt'
},
{
text: 'opt 2'
},
{
text: 'opt 3'
}
])
})
test('invalid id gets null as result', () => {
const res = store.getOptionByID('0000')
expect(res).toBeNull()
})
test('valid id gets correct result', () => {
const res = store.getOptionByID('12345678')
expect(res).toBeInstanceOf(Option)
expect((res as Option).text).toBe('id opt')
})
})
describe('getSelectType', () => {
test('get correct type on single select', () => {
const store = new Store('single', [])
expect(store.getSelectType()).toBe('single')
})
test('get correct type on multiple select', () => {
const store = new Store('multiple', [])
expect(store.getSelectType()).toBe('multiple')
})
})
describe('getFirstOption', () => {
test('getFirstOption returns first option', () => {
const flatStore = new Store('single', [
{
text: 'test0'
},
{
text: 'test1'
}
])
expect(flatStore.getFirstOption()?.text).toBe('test0')
const store = new Store('single', [
{
label: 'group0',
options: [
{
text: 'test0'
},
{
text: 'test1'
}
]
},
{
text: 'test2'
}
])
expect(store.getFirstOption()?.text).toBe('test0')
})
})
describe('search', () => {
test('search with term returns correct option', () => {
const store = new Store('single', [
{
text: 'test1'
},
{
text: 'test2',
value: 'test2'
},
{
text: 'test3'
}
])
const searchFilter = (opt: Option, search: string): boolean => {
return opt.text.toLowerCase().indexOf(search.toLowerCase()) !== -1
}
// With searchFilter search against current store data set
const searchResults = store.search('test2', searchFilter)
expect(searchResults).toHaveLength(1)
expect((searchResults[0] as Option).value).toBe('test2')
})
test('empty search term returns all options', () => {
const store = new Store('single', [
{
text: 'test1'
},
{
text: 'test2',
value: 'test2'
},
{
text: 'test3'
}
])
const searchFilter = (opt: Option, search: string): boolean => {
return opt.text.toLowerCase().indexOf(search.toLowerCase()) !== -1
}
// Test empty search term
const searchResults = store.search('', searchFilter)
expect(searchResults).toHaveLength(3)
})
})
describe('filter', () => {
let store: Store
beforeAll(() => {
store = new Store('single', [
{
label: 'group 0',
options: [
{
text: 'opt 0',
class: 'filter-me'
},
{
text: 'opt 1'
}
]
},
{
text: 'opt 2',
class: 'filter-me'
}
])
})
test('empty filter function returns all options (with opt group)', () => {
const res = store.filter(null, true)
expect(res).toHaveLength(2)
expect(res[0]).toBeInstanceOf(Optgroup)
expect((res[0] as Optgroup).label).toBe('group 0')
expect((res[0] as Optgroup).options).toHaveLength(2)
expect(res[1]).toBeInstanceOf(Option)
})
test('empty filter function returns all options without optgroups flattens results', () => {
const res = store.filter(null, false)
expect(res).toHaveLength(3)
expect(res[0]).toBeInstanceOf(Option)
expect((res[0] as Option).text).toBe('opt 0')
expect(res[1]).toBeInstanceOf(Option)
expect((res[1] as Option).text).toBe('opt 1')
expect(res[2]).toBeInstanceOf(Option)
expect((res[2] as Option).text).toBe('opt 2')
})
test('filter function filters results accordingly', () => {
const res = store.filter((o) => o.class === 'filter-me', false)
expect(res).toHaveLength(2)
expect(res[0]).toBeInstanceOf(Option)
expect((res[0] as Option).text).toBe('opt 0')
expect(res[1]).toBeInstanceOf(Option)
expect((res[1] as Option).text).toBe('opt 2')
})
})
})