slim-select 2.8.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +1 -0
- package/.prettierrc +4 -3
- package/README.md +2 -0
- package/dist/classes.d.ts +43 -0
- package/dist/helpers.d.ts +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/render.d.ts +4 -44
- package/dist/select.d.ts +4 -2
- package/dist/settings.d.ts +2 -1
- package/dist/slimselect.cjs.js +248 -120
- package/dist/slimselect.css +1 -1
- package/dist/slimselect.css.map +1 -1
- package/dist/slimselect.es.js +248 -120
- package/dist/slimselect.global.js +248 -120
- package/dist/slimselect.js +248 -120
- package/dist/slimselect.min.js +1 -1
- package/dist/slimselect.umd.js +248 -120
- package/dist/slimselect.umd.min.js +1 -1
- package/dist/store.d.ts +4 -2
- package/index.html +1 -4
- package/jest.config.js +6 -0
- package/package.json +30 -41
- package/src/slim-select/classes.test.ts +72 -0
- package/src/slim-select/classes.ts +109 -0
- package/src/slim-select/helpers.test.ts +160 -0
- package/src/slim-select/helpers.ts +3 -12
- package/src/slim-select/index.test.ts +109 -16
- package/src/slim-select/index.ts +41 -12
- package/src/slim-select/render.test.ts +1169 -18
- package/src/slim-select/render.ts +75 -96
- package/src/slim-select/rollup.config.mjs +28 -28
- package/src/slim-select/select.test.ts +306 -156
- package/src/slim-select/select.ts +69 -20
- package/src/slim-select/settings.test.ts +257 -0
- package/src/slim-select/settings.ts +4 -0
- package/src/slim-select/slimselect.scss +7 -3
- package/src/slim-select/store.test.ts +986 -235
- package/src/slim-select/store.ts +70 -28
- package/src/{vue/slimselect.vue → slim-select/vue.vue} +19 -17
- package/tsconfig.json +2 -3
- package/src/react/.eslintignore +0 -2
- package/src/react/.eslintrc.cjs +0 -24
- package/src/react/index.html +0 -14
- package/src/react/jestconfig.json +0 -11
- package/src/react/package.json +0 -32
- package/src/react/src/frameworkpage.tsx +0 -133
- package/src/react/src/index.ts +0 -3
- package/src/react/src/slimselect.tsx +0 -148
- package/src/react/tests/slimselect.test.tsx +0 -52
- package/src/react/tsconfig.json +0 -37
- package/src/react/tsconfig.node.json +0 -10
- package/src/react/vite.config.ts +0 -55
- package/src/vue/README.md +0 -48
- package/src/vue/dist/slim-select/helpers.d.ts +0 -5
- package/src/vue/dist/slim-select/index.d.ts +0 -46
- package/src/vue/dist/slim-select/render.d.ts +0 -120
- package/src/vue/dist/slim-select/select.d.ts +0 -28
- package/src/vue/dist/slim-select/settings.d.ts +0 -34
- package/src/vue/dist/slim-select/store.d.ts +0 -80
- package/src/vue/dist/slimselectvue.es.js +0 -1978
- package/src/vue/dist/slimselectvue.global.js +0 -1981
- package/src/vue/dist/slimselectvue.ssr.js +0 -1980
- package/src/vue/dist/vue/hello.d.ts +0 -2
- package/src/vue/dist/vue/slimselect.vue.d.ts +0 -326
- package/src/vue/hello.ts +0 -5
- package/src/vue/package.json +0 -28
- package/src/vue/rollup.config.mjs +0 -44
- package/src/vue/tsconfig.json +0 -14
- /package/{vite.config.ts → vite.config.mts} +0 -0
|
@@ -7,33 +7,1184 @@
|
|
|
7
7
|
import { describe, expect, test } from '@jest/globals'
|
|
8
8
|
import Render, { Callbacks } from './render'
|
|
9
9
|
import Settings from './settings'
|
|
10
|
-
import Store, {
|
|
10
|
+
import Store, { Option } from './store'
|
|
11
|
+
import CssClasses from './classes'
|
|
11
12
|
|
|
12
|
-
describe('
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
describe('render module', () => {
|
|
14
|
+
let render: Render
|
|
15
|
+
let openMock: jest.Mock
|
|
16
|
+
let closeMock: jest.Mock
|
|
17
|
+
let addSelectedMock: jest.Mock
|
|
18
|
+
let setSelectedMock: jest.Mock
|
|
19
|
+
let addOptionMock: jest.Mock
|
|
20
|
+
let searchMock: jest.Mock
|
|
21
|
+
let afterChangeMock: jest.Mock
|
|
22
|
+
let beforeChangeMock: jest.Mock
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
15
25
|
const store = new Store('single', [
|
|
16
|
-
{
|
|
17
|
-
|
|
26
|
+
{
|
|
27
|
+
text: 'test0',
|
|
28
|
+
value: 'test0'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
text: 'test1',
|
|
32
|
+
value: 'test1',
|
|
33
|
+
html: '<span>test1</span>'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
text: 'test2',
|
|
37
|
+
selected: true
|
|
38
|
+
}
|
|
18
39
|
])
|
|
19
40
|
|
|
20
41
|
// default settings
|
|
21
42
|
const settings = new Settings()
|
|
43
|
+
const classes = new CssClasses()
|
|
44
|
+
|
|
45
|
+
openMock = jest.fn(() => {})
|
|
46
|
+
closeMock = jest.fn(() => {})
|
|
47
|
+
addSelectedMock = jest.fn(() => {})
|
|
48
|
+
setSelectedMock = jest.fn(() => {})
|
|
49
|
+
addOptionMock = jest.fn(() => {})
|
|
50
|
+
searchMock = jest.fn(() => {})
|
|
51
|
+
afterChangeMock = jest.fn(() => {})
|
|
52
|
+
beforeChangeMock = jest.fn(() => true)
|
|
22
53
|
|
|
23
54
|
// default callbacks
|
|
24
55
|
const callbacks = {
|
|
25
|
-
open:
|
|
26
|
-
close:
|
|
27
|
-
addSelected:
|
|
28
|
-
setSelected:
|
|
29
|
-
addOption:
|
|
30
|
-
search:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
open: openMock,
|
|
57
|
+
close: closeMock,
|
|
58
|
+
addSelected: addSelectedMock,
|
|
59
|
+
setSelected: setSelectedMock,
|
|
60
|
+
addOption: addOptionMock,
|
|
61
|
+
search: searchMock,
|
|
62
|
+
afterChange: afterChangeMock,
|
|
63
|
+
beforeChange: beforeChangeMock
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
render = new Render(settings, classes, store, callbacks)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('constructor', () => {
|
|
70
|
+
test('default constructor works', () => {
|
|
71
|
+
// create a new store with 2 options
|
|
72
|
+
const store = new Store('single', [
|
|
73
|
+
{ text: 'test1', value: 'test1' },
|
|
74
|
+
{ text: 'test2', value: 'test2' }
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
// default settings
|
|
78
|
+
const settings = new Settings()
|
|
79
|
+
const classes = new CssClasses()
|
|
80
|
+
|
|
81
|
+
// default callbacks
|
|
82
|
+
const callbacks = {
|
|
83
|
+
open: () => {},
|
|
84
|
+
close: () => {},
|
|
85
|
+
addSelected: () => {},
|
|
86
|
+
setSelected: () => {},
|
|
87
|
+
addOption: () => {},
|
|
88
|
+
search: () => {},
|
|
89
|
+
beforeChange: () => {
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
} as Callbacks
|
|
93
|
+
|
|
94
|
+
const render = new Render(settings, classes, store, callbacks)
|
|
95
|
+
expect(render).toBeInstanceOf(Render)
|
|
96
|
+
expect(render.main.main).toBeInstanceOf(HTMLDivElement)
|
|
97
|
+
expect(render.content.search.input).toBeInstanceOf(HTMLInputElement)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe('enable', () => {
|
|
102
|
+
test('enable removes disabled class from main and enables search input', () => {
|
|
103
|
+
// disable stuff directly
|
|
104
|
+
render.main.main.classList.add(render.classes.disabled)
|
|
105
|
+
render.content.search.input.disabled = true
|
|
106
|
+
|
|
107
|
+
render.enable()
|
|
108
|
+
expect(render.main.main.classList.contains(render.classes.disabled)).toBe(false)
|
|
109
|
+
expect(render.content.search.input.disabled).toBe(false)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('disable', () => {
|
|
114
|
+
test('disable adds disabled class to main and disables search input', () => {
|
|
115
|
+
render.disable()
|
|
116
|
+
expect(render.main.main.classList.contains(render.classes.disabled)).toBe(true)
|
|
117
|
+
expect(render.content.search.input.disabled).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('open', () => {
|
|
122
|
+
test('open sets the correct attributes and CSS classes', () => {
|
|
123
|
+
render.open()
|
|
124
|
+
|
|
125
|
+
expect(render.main.arrow.path.getAttribute('d')).toBe(render.classes.arrowOpen)
|
|
126
|
+
expect(render.main.main.classList.contains(render.classes.openBelow)).toBe(true)
|
|
127
|
+
expect(render.main.main.classList.contains(render.classes.openAbove)).toBe(false)
|
|
128
|
+
expect(render.main.main.getAttribute('aria-expanded')).toBe('true')
|
|
129
|
+
expect(render.content.main.classList.contains(render.classes.openBelow)).toBe(true)
|
|
130
|
+
expect(render.content.main.classList.contains(render.classes.openAbove)).toBe(false)
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe('close', () => {
|
|
135
|
+
test('close sets the correct attributes and CSS classes', () => {
|
|
136
|
+
render.close()
|
|
137
|
+
|
|
138
|
+
expect(render.main.arrow.path.getAttribute('d')).toBe(render.classes.arrowClose)
|
|
139
|
+
expect(render.main.main.classList.contains(render.classes.openBelow)).toBe(false)
|
|
140
|
+
expect(render.main.main.classList.contains(render.classes.openAbove)).toBe(false)
|
|
141
|
+
expect(render.main.main.getAttribute('aria-expanded')).toBe('false')
|
|
142
|
+
expect(render.content.main.classList.contains(render.classes.openBelow)).toBe(false)
|
|
143
|
+
expect(render.content.main.classList.contains(render.classes.openAbove)).toBe(false)
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
describe('updateClassStyles', () => {
|
|
148
|
+
test('existing classes and styles are cleared', () => {
|
|
149
|
+
// manually set classes and styles for testing
|
|
150
|
+
render.main.main.className = 'test'
|
|
151
|
+
render.main.main.style.color = 'red'
|
|
152
|
+
|
|
153
|
+
render.content.main.className = 'test'
|
|
154
|
+
render.content.main.style.color = 'red'
|
|
155
|
+
|
|
156
|
+
render.updateClassStyles()
|
|
157
|
+
|
|
158
|
+
expect(render.main.main.classList.contains('test')).toBe(false)
|
|
159
|
+
expect(render.main.main.style.color).toBeFalsy()
|
|
160
|
+
|
|
161
|
+
expect(render.content.main.classList.contains('test')).toBe(false)
|
|
162
|
+
expect(render.content.main.style.color).toBeFalsy()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('inline styles are applied to main elements', () => {
|
|
166
|
+
render.settings.style = 'color: red'
|
|
167
|
+
|
|
168
|
+
render.updateClassStyles()
|
|
169
|
+
|
|
170
|
+
expect(render.main.main.style.color).toBe('red')
|
|
171
|
+
expect(render.content.main.style.color).toBe('red')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('classes are applied to main elements', () => {
|
|
175
|
+
render.settings.class = ['test0', 'test1', 'test2']
|
|
176
|
+
|
|
177
|
+
render.updateClassStyles()
|
|
178
|
+
|
|
179
|
+
expect(render.main.main.classList.contains('test0')).toBe(true)
|
|
180
|
+
expect(render.main.main.classList.contains('test1')).toBe(true)
|
|
181
|
+
expect(render.main.main.classList.contains('test2')).toBe(true)
|
|
182
|
+
|
|
183
|
+
expect(render.content.main.classList.contains('test0')).toBe(true)
|
|
184
|
+
expect(render.content.main.classList.contains('test1')).toBe(true)
|
|
185
|
+
expect(render.content.main.classList.contains('test2')).toBe(true)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('if content position is relative, class is added on content', () => {
|
|
189
|
+
render.settings.contentPosition = 'relative'
|
|
190
|
+
|
|
191
|
+
render.updateClassStyles()
|
|
192
|
+
|
|
193
|
+
expect(render.content.main.classList.contains('ss-relative')).toBe(true)
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe('updateAriaAttributes', () => {
|
|
198
|
+
test('sets correct aria attributes', () => {
|
|
199
|
+
render.updateAriaAttributes()
|
|
200
|
+
|
|
201
|
+
expect(render.main.main.role).toBe('combobox')
|
|
202
|
+
expect(render.main.main.getAttribute('aria-haspopup')).toBe('listbox')
|
|
203
|
+
expect(render.main.main.getAttribute('aria-controls')).toBe(render.content.main.id)
|
|
204
|
+
expect(render.main.main.getAttribute('aria-expanded')).toBe('false')
|
|
205
|
+
expect(render.content.main.getAttribute('role')).toBe('listbox')
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
describe('mainDiv', () => {
|
|
210
|
+
test('correct HTML element gets created', () => {
|
|
211
|
+
const main = render.main.main
|
|
212
|
+
|
|
213
|
+
expect(main.dataset.id).toBe(render.settings.id)
|
|
214
|
+
expect(main.getAttribute('aria-label')).toBe(render.settings.ariaLabel)
|
|
215
|
+
expect(main.tabIndex).toBe(0)
|
|
216
|
+
expect(main.children).toHaveLength(3)
|
|
217
|
+
expect(main.children.item(0)?.className).toBe(render.classes.values)
|
|
218
|
+
expect(main.children.item(1)?.classList.contains(render.classes.deselect)).toBe(true)
|
|
219
|
+
expect(main.children.item(1)?.classList.contains(render.classes.hide)).toBe(true)
|
|
220
|
+
expect(main.children.item(1)?.children).toHaveLength(1)
|
|
221
|
+
expect(main.children.item(1)?.children).toHaveLength(1)
|
|
222
|
+
expect(main.children.item(1)?.children.item(0)).toBeInstanceOf(SVGElement)
|
|
223
|
+
expect(main.children.item(2)?.classList.contains(render.classes.arrow)).toBe(true)
|
|
224
|
+
expect(main.children.item(2)?.classList.contains(render.classes.hide)).toBe(false)
|
|
225
|
+
expect(main.children.item(2)?.children.item(0)).toBeInstanceOf(SVGElement)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('arrow is hidden when alwaysOpen is set', () => {
|
|
229
|
+
render.settings.alwaysOpen = true
|
|
230
|
+
const main = render.mainDiv().main
|
|
231
|
+
|
|
232
|
+
expect(main.children.item(2)?.classList.contains(render.classes.arrow)).toBe(true)
|
|
233
|
+
expect(main.children.item(2)?.classList.contains(render.classes.hide)).toBe(true)
|
|
234
|
+
expect(main.children.item(2)?.children.item(0)).toBeInstanceOf(SVGElement)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test('arrow key events on main element move highlight', () => {
|
|
238
|
+
const highlightMock = jest.fn(() => {})
|
|
239
|
+
|
|
240
|
+
render.highlight = highlightMock
|
|
241
|
+
|
|
242
|
+
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
|
|
243
|
+
expect(openMock).toHaveBeenCalled()
|
|
244
|
+
expect(highlightMock).toHaveBeenCalledTimes(1)
|
|
245
|
+
expect(highlightMock.mock.calls[0]).toStrictEqual(['up'])
|
|
246
|
+
|
|
247
|
+
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
|
|
248
|
+
expect(openMock).toHaveBeenCalledTimes(2)
|
|
249
|
+
expect(highlightMock).toHaveBeenCalledTimes(2)
|
|
250
|
+
expect(highlightMock.mock.calls[1]).toStrictEqual(['down'])
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
test('tab and escape key event on main element triggers close callback', () => {
|
|
254
|
+
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }))
|
|
255
|
+
expect(closeMock).toHaveBeenCalled()
|
|
256
|
+
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
257
|
+
expect(closeMock).toHaveBeenCalledTimes(2)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test('enter and space key event on main element triggers open callback', () => {
|
|
261
|
+
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
|
|
262
|
+
expect(openMock).toHaveBeenCalled()
|
|
263
|
+
render.main.main.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }))
|
|
264
|
+
expect(openMock).toHaveBeenCalledTimes(2)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test('click on main event does nothing if element is disabled', () => {
|
|
268
|
+
render.settings.disabled = true
|
|
269
|
+
|
|
270
|
+
render.main.main.dispatchEvent(new MouseEvent('click'))
|
|
271
|
+
|
|
272
|
+
expect(openMock).not.toHaveBeenCalled()
|
|
273
|
+
expect(closeMock).not.toHaveBeenCalled()
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
test('click on main event triggers open if element is closed', () => {
|
|
277
|
+
render.main.main.dispatchEvent(new MouseEvent('click'))
|
|
278
|
+
|
|
279
|
+
expect(openMock).toHaveBeenCalled()
|
|
280
|
+
expect(closeMock).not.toHaveBeenCalled()
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
test('click on main event triggers close if element is opened', () => {
|
|
284
|
+
render.settings.isOpen = true
|
|
285
|
+
|
|
286
|
+
render.main.main.dispatchEvent(new MouseEvent('click'))
|
|
287
|
+
|
|
288
|
+
expect(openMock).not.toHaveBeenCalled()
|
|
289
|
+
expect(closeMock).toHaveBeenCalled()
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test('click on deselect does nothing if element is disabled', () => {
|
|
293
|
+
render.settings.disabled = true
|
|
294
|
+
|
|
295
|
+
const deselectElement: HTMLDivElement = render.main.main.querySelector(
|
|
296
|
+
'.' + render.classes.deselect
|
|
297
|
+
) as HTMLDivElement
|
|
298
|
+
|
|
299
|
+
deselectElement.dispatchEvent(new MouseEvent('click'))
|
|
35
300
|
|
|
36
|
-
|
|
37
|
-
|
|
301
|
+
expect(afterChangeMock).not.toHaveBeenCalled()
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
test('click on deselect on single select runs callbacks', () => {
|
|
305
|
+
const deselectElement: HTMLDivElement = render.main.main.querySelector(
|
|
306
|
+
'.' + render.classes.deselect
|
|
307
|
+
) as HTMLDivElement
|
|
308
|
+
|
|
309
|
+
deselectElement.dispatchEvent(new MouseEvent('click'))
|
|
310
|
+
|
|
311
|
+
expect(setSelectedMock).toHaveBeenCalled()
|
|
312
|
+
expect(closeMock).toHaveBeenCalled()
|
|
313
|
+
expect(afterChangeMock).toHaveBeenCalled()
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
test('click on deselect on multiple select runs callbacks', () => {
|
|
317
|
+
render.settings.isMultiple = true
|
|
318
|
+
|
|
319
|
+
const deselectAllMock = jest.fn()
|
|
320
|
+
|
|
321
|
+
render.updateDeselectAll = deselectAllMock
|
|
322
|
+
|
|
323
|
+
const deselectElement: HTMLDivElement = render.main.main.querySelector(
|
|
324
|
+
'.' + render.classes.deselect
|
|
325
|
+
) as HTMLDivElement
|
|
326
|
+
deselectElement.dispatchEvent(new MouseEvent('click'))
|
|
327
|
+
|
|
328
|
+
expect(deselectAllMock).toHaveBeenCalled()
|
|
329
|
+
expect(setSelectedMock).toHaveBeenCalled()
|
|
330
|
+
expect(closeMock).toHaveBeenCalled()
|
|
331
|
+
expect(afterChangeMock).toHaveBeenCalled()
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
describe('mainFocus', () => {
|
|
336
|
+
let focusMock: jest.Mock
|
|
337
|
+
|
|
338
|
+
beforeEach(() => {
|
|
339
|
+
focusMock = jest.fn(() => {})
|
|
340
|
+
render.main.main.focus = focusMock
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
test('mainFocus does nothing if the event is click', () => {
|
|
344
|
+
render.mainFocus('click')
|
|
345
|
+
expect(focusMock).not.toHaveBeenCalled()
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
test('mainFocus triggers focus if the event is not click', () => {
|
|
349
|
+
render.mainFocus('keydown')
|
|
350
|
+
expect(focusMock).toHaveBeenCalled()
|
|
351
|
+
render.mainFocus('keyup')
|
|
352
|
+
expect(focusMock).toHaveBeenCalledTimes(2)
|
|
353
|
+
render.mainFocus('mouse')
|
|
354
|
+
expect(focusMock).toHaveBeenCalledTimes(3)
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
describe('placeholder', () => {
|
|
359
|
+
test('placeholder uses fallback text if no option is found', () => {
|
|
360
|
+
render.settings.placeholderText = 'placeholder text'
|
|
361
|
+
const placeholder = render.placeholder()
|
|
362
|
+
|
|
363
|
+
expect(placeholder.innerHTML).toBe(render.settings.placeholderText)
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
test('placeholder uses option html if option is found', () => {
|
|
367
|
+
render.store.setData([
|
|
368
|
+
{
|
|
369
|
+
text: 'opt text',
|
|
370
|
+
html: '<h1>Option HTML</h1>',
|
|
371
|
+
placeholder: true
|
|
372
|
+
}
|
|
373
|
+
])
|
|
374
|
+
const placeholder = render.placeholder()
|
|
375
|
+
|
|
376
|
+
expect(placeholder.innerHTML).toBe('<h1>Option HTML</h1>')
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
test('placeholder uses option text if option is found and no HTML is set', () => {
|
|
380
|
+
render.store.setData([
|
|
381
|
+
{
|
|
382
|
+
text: 'opt text',
|
|
383
|
+
placeholder: true
|
|
384
|
+
}
|
|
385
|
+
])
|
|
386
|
+
const placeholder = render.placeholder()
|
|
387
|
+
|
|
388
|
+
expect(placeholder.innerHTML).toBe('opt text')
|
|
389
|
+
})
|
|
38
390
|
})
|
|
391
|
+
|
|
392
|
+
describe('renderValues', () => {
|
|
393
|
+
test('single select renders only one value', () => {
|
|
394
|
+
render.renderValues()
|
|
395
|
+
|
|
396
|
+
expect(render.main.values.children).toHaveLength(1)
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
test('single select renders HTML option', () => {
|
|
400
|
+
render.store.setData([
|
|
401
|
+
{
|
|
402
|
+
text: 'opt0'
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
text: 'opt1',
|
|
406
|
+
html: '<span>opt1</span>',
|
|
407
|
+
selected: true
|
|
408
|
+
}
|
|
409
|
+
])
|
|
410
|
+
render.renderValues()
|
|
411
|
+
|
|
412
|
+
expect(render.main.values.children).toHaveLength(1)
|
|
413
|
+
expect(render.main.values.children.item(0)?.innerHTML).toBe('<span>opt1</span>')
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
test('multiple select renders all selected values', () => {
|
|
417
|
+
render.settings.isMultiple = true
|
|
418
|
+
render.store = new Store('multiple', [
|
|
419
|
+
{
|
|
420
|
+
text: 'opt0',
|
|
421
|
+
value: 'opt0',
|
|
422
|
+
selected: true
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
text: 'opt1',
|
|
426
|
+
value: 'opt1',
|
|
427
|
+
html: '<span>opt1</span>',
|
|
428
|
+
selected: true
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
text: 'opt2'
|
|
432
|
+
}
|
|
433
|
+
])
|
|
434
|
+
|
|
435
|
+
render.renderValues()
|
|
436
|
+
|
|
437
|
+
expect(render.main.values.children).toHaveLength(2)
|
|
438
|
+
expect(render.main.values.children.item(0)).toBeInstanceOf(HTMLDivElement)
|
|
439
|
+
expect((render.main.values.children.item(0) as HTMLDivElement).textContent).toBe('opt0')
|
|
440
|
+
expect(render.main.values.children.item(1)).toBeInstanceOf(HTMLDivElement)
|
|
441
|
+
expect((render.main.values.children.item(1) as HTMLDivElement).textContent).toBe('opt1')
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
test('multiple select renders counter element when maxValuesShown is set', () => {
|
|
445
|
+
render.settings.isMultiple = true
|
|
446
|
+
render.settings.maxValuesShown = 2
|
|
447
|
+
render.store = new Store('multiple', [
|
|
448
|
+
{
|
|
449
|
+
text: 'opt0',
|
|
450
|
+
value: 'opt0',
|
|
451
|
+
selected: true
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
text: 'opt1',
|
|
455
|
+
value: 'opt1',
|
|
456
|
+
html: '<span>opt1</span>',
|
|
457
|
+
selected: true
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
text: 'opt2',
|
|
461
|
+
value: 'opt2',
|
|
462
|
+
selected: true
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
text: 'opt4'
|
|
466
|
+
}
|
|
467
|
+
])
|
|
468
|
+
|
|
469
|
+
render.renderValues()
|
|
470
|
+
|
|
471
|
+
expect(render.main.values.children).toHaveLength(1)
|
|
472
|
+
expect(render.main.values.children.item(0)?.innerHTML).toBe('3 selected')
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
test('remove old options from values', () => {
|
|
476
|
+
render.renderValues()
|
|
477
|
+
expect(render.main.values.children).toHaveLength(1)
|
|
478
|
+
expect(render.main.values.innerText).toBeFalsy()
|
|
479
|
+
|
|
480
|
+
render.store.setSelectedBy('value', ['test1'])
|
|
481
|
+
render.renderValues()
|
|
482
|
+
|
|
483
|
+
expect(render.main.values.children).toHaveLength(1)
|
|
484
|
+
expect(render.main.values.children.item(0)?.innerHTML).toBe('<span>test1</span>')
|
|
485
|
+
})
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
describe('multipleValue', () => {})
|
|
489
|
+
|
|
490
|
+
describe('contentDiv', () => {})
|
|
491
|
+
|
|
492
|
+
describe('moveContent', () => {
|
|
493
|
+
let contentAboveMock: jest.Mock
|
|
494
|
+
let contentBelowMock: jest.Mock
|
|
495
|
+
|
|
496
|
+
beforeEach(() => {
|
|
497
|
+
contentAboveMock = jest.fn()
|
|
498
|
+
contentBelowMock = jest.fn()
|
|
499
|
+
|
|
500
|
+
render.moveContentAbove = contentAboveMock
|
|
501
|
+
render.moveContentBelow = contentBelowMock
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
test('content is moved below when position is relative', () => {
|
|
505
|
+
render.settings.contentPosition = 'relative'
|
|
506
|
+
|
|
507
|
+
render.moveContent()
|
|
508
|
+
expect(contentAboveMock).not.toHaveBeenCalled()
|
|
509
|
+
expect(contentBelowMock).toHaveBeenCalled()
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
test('content is moved below when open position is down', () => {
|
|
513
|
+
render.settings.openPosition = 'down'
|
|
514
|
+
|
|
515
|
+
render.moveContent()
|
|
516
|
+
expect(contentAboveMock).not.toHaveBeenCalled()
|
|
517
|
+
expect(contentBelowMock).toHaveBeenCalled()
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
test('content is moved above when open position is up', () => {
|
|
521
|
+
render.settings.openPosition = 'up'
|
|
522
|
+
|
|
523
|
+
render.moveContent()
|
|
524
|
+
expect(contentAboveMock).toHaveBeenCalled()
|
|
525
|
+
expect(contentBelowMock).not.toHaveBeenCalled()
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
test('content is moved above when putContent is up', () => {
|
|
529
|
+
render.putContent = jest.fn(() => 'up')
|
|
530
|
+
|
|
531
|
+
render.moveContent()
|
|
532
|
+
expect(contentAboveMock).toHaveBeenCalled()
|
|
533
|
+
expect(contentBelowMock).not.toHaveBeenCalled()
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
test('content is moved below when putContent is down', () => {
|
|
537
|
+
render.putContent = jest.fn(() => 'down')
|
|
538
|
+
|
|
539
|
+
render.moveContent()
|
|
540
|
+
expect(contentAboveMock).not.toHaveBeenCalled()
|
|
541
|
+
expect(contentBelowMock).toHaveBeenCalled()
|
|
542
|
+
})
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
describe('searchDiv', () => {
|
|
546
|
+
test('search is hidden when showSearch setting is false', () => {
|
|
547
|
+
render.settings.showSearch = false
|
|
548
|
+
|
|
549
|
+
const search = render.searchDiv()
|
|
550
|
+
|
|
551
|
+
expect(search.main.classList.contains(render.classes.hide)).toBe(true)
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
test('input is debounced', async () => {
|
|
555
|
+
const search = render.searchDiv()
|
|
556
|
+
|
|
557
|
+
search.input.dispatchEvent(new InputEvent('input', { data: 'asdf' }))
|
|
558
|
+
|
|
559
|
+
// wait for debounce to trigger
|
|
560
|
+
await new Promise((r) => setTimeout(r, 101))
|
|
561
|
+
|
|
562
|
+
expect(searchMock).toHaveBeenCalled()
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
test('arrow keys move highlight', () => {
|
|
566
|
+
const search = render.searchDiv()
|
|
567
|
+
const highlightMock = jest.fn(() => {})
|
|
568
|
+
|
|
569
|
+
render.highlight = highlightMock
|
|
570
|
+
|
|
571
|
+
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
|
|
572
|
+
expect(highlightMock).toHaveBeenCalledTimes(1)
|
|
573
|
+
expect(highlightMock.mock.calls[0]).toStrictEqual(['up'])
|
|
574
|
+
|
|
575
|
+
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
|
|
576
|
+
expect(highlightMock).toHaveBeenCalledTimes(2)
|
|
577
|
+
expect(highlightMock.mock.calls[1]).toStrictEqual(['down'])
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
test('tab triggers close callback', () => {
|
|
581
|
+
const search = render.searchDiv()
|
|
582
|
+
|
|
583
|
+
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }))
|
|
584
|
+
|
|
585
|
+
expect(closeMock).toHaveBeenCalled()
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
test('escape triggers close callback', () => {
|
|
589
|
+
// separate test in case we want to also test the event someday
|
|
590
|
+
const search = render.searchDiv()
|
|
591
|
+
|
|
592
|
+
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
593
|
+
|
|
594
|
+
expect(closeMock).toHaveBeenCalled()
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
test("enter and space don't call addable witout ctrl key", () => {
|
|
598
|
+
const search = render.searchDiv()
|
|
599
|
+
const addableMock = jest.fn((s: string) => ({
|
|
600
|
+
text: s,
|
|
601
|
+
value: s.toLowerCase()
|
|
602
|
+
}))
|
|
603
|
+
|
|
604
|
+
render.callbacks.addable = addableMock
|
|
605
|
+
|
|
606
|
+
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
|
|
607
|
+
expect(addableMock).not.toHaveBeenCalled()
|
|
608
|
+
|
|
609
|
+
search.input.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }))
|
|
610
|
+
expect(addableMock).not.toHaveBeenCalled()
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
test('enter and space call event and does nothing without input value', () => {
|
|
614
|
+
const addableMock = jest.fn((s: string) => ({
|
|
615
|
+
text: s,
|
|
616
|
+
value: s.toLowerCase()
|
|
617
|
+
}))
|
|
618
|
+
|
|
619
|
+
render.callbacks.addable = addableMock
|
|
620
|
+
|
|
621
|
+
// recreate search because we have added the addable callback
|
|
622
|
+
render.content.search = render.searchDiv()
|
|
623
|
+
|
|
624
|
+
render.content.search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
|
|
625
|
+
expect(addableMock).not.toHaveBeenCalled()
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
test('enter and space call addable when defined', () => {
|
|
629
|
+
const addableMock = jest.fn((s: string) => ({
|
|
630
|
+
text: s,
|
|
631
|
+
value: s.toLowerCase()
|
|
632
|
+
}))
|
|
633
|
+
|
|
634
|
+
render.callbacks.addable = addableMock
|
|
635
|
+
|
|
636
|
+
// recreate search because we have added the addable callback
|
|
637
|
+
render.content.search = render.searchDiv()
|
|
638
|
+
|
|
639
|
+
render.content.search.input.value = 'Search'
|
|
640
|
+
|
|
641
|
+
render.content.search.input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
|
|
642
|
+
expect(addableMock).toHaveBeenCalledTimes(1)
|
|
643
|
+
expect(addableMock.mock.calls[0]).toStrictEqual(['Search'])
|
|
644
|
+
})
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
describe('searchFocus', () => {
|
|
648
|
+
test('search is focused', () => {
|
|
649
|
+
expect(document.activeElement).not.toBe(render.content.search.input)
|
|
650
|
+
|
|
651
|
+
render.searchFocus()
|
|
652
|
+
expect(document.activeElement).toBe(render.content.search.input)
|
|
653
|
+
})
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
describe('getOptions', () => {
|
|
657
|
+
test('returns all options when called without parameters', () => {
|
|
658
|
+
// render all 3 default options and get them back
|
|
659
|
+
render.renderOptions(render.store.getDataOptions())
|
|
660
|
+
const opts = render.getOptions()
|
|
661
|
+
|
|
662
|
+
expect(opts).toHaveLength(3)
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
test('filters correctly when filtering out placeholders', () => {
|
|
666
|
+
render.renderOptions(
|
|
667
|
+
render.store.partialToFullData([
|
|
668
|
+
{
|
|
669
|
+
text: 'opt0',
|
|
670
|
+
placeholder: true
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
text: 'opt1'
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
text: 'opt2'
|
|
677
|
+
}
|
|
678
|
+
])
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
const opts = render.getOptions(true, false, true)
|
|
682
|
+
expect(opts).toHaveLength(2)
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
test('filters correctly when filtering out disabled options', () => {
|
|
686
|
+
render.renderOptions(
|
|
687
|
+
render.store.partialToFullData([
|
|
688
|
+
{
|
|
689
|
+
text: 'opt0',
|
|
690
|
+
disabled: true
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
text: 'opt1'
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
text: 'opt2'
|
|
697
|
+
}
|
|
698
|
+
])
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
const opts = render.getOptions(false, true)
|
|
702
|
+
expect(opts).toHaveLength(2)
|
|
703
|
+
})
|
|
704
|
+
|
|
705
|
+
test('filters correctly when filtering out hidden options', () => {
|
|
706
|
+
render.renderOptions(
|
|
707
|
+
render.store.partialToFullData([
|
|
708
|
+
{
|
|
709
|
+
text: 'opt0',
|
|
710
|
+
placeholder: true
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
text: 'opt1'
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
text: 'opt2'
|
|
717
|
+
}
|
|
718
|
+
])
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
const opts = render.getOptions(false, false, true)
|
|
722
|
+
expect(opts).toHaveLength(2)
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
test('filters correctly when filtering out hidden and disabled options', () => {
|
|
726
|
+
render.renderOptions(
|
|
727
|
+
render.store.partialToFullData([
|
|
728
|
+
{
|
|
729
|
+
text: 'opt0',
|
|
730
|
+
disabled: true
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
text: 'opt1',
|
|
734
|
+
placeholder: true
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
text: 'opt2'
|
|
738
|
+
}
|
|
739
|
+
])
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
const opts = render.getOptions(false, true, true)
|
|
743
|
+
expect(opts).toHaveLength(1)
|
|
744
|
+
})
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
describe('highlight', () => {
|
|
748
|
+
test('simply do nothing without breaking when options are empty', () => {
|
|
749
|
+
render.renderOptions([])
|
|
750
|
+
|
|
751
|
+
expect(() => render.highlight('up')).not.toThrowError()
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
test('highlight single option that is not already highlighted', () => {
|
|
755
|
+
render.renderOptions(
|
|
756
|
+
render.store.partialToFullData([
|
|
757
|
+
{
|
|
758
|
+
text: 'opt0'
|
|
759
|
+
}
|
|
760
|
+
])
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
render.highlight('up')
|
|
764
|
+
|
|
765
|
+
expect(render.getOptions()[0].classList.contains(render.classes.highlighted)).toBe(true)
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
test('select first option with down when no option is highlighted or selected', () => {
|
|
769
|
+
render.renderOptions(
|
|
770
|
+
render.store.partialToFullData([
|
|
771
|
+
{
|
|
772
|
+
text: 'opt0'
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
text: 'opt1'
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
text: 'opt2'
|
|
779
|
+
}
|
|
780
|
+
])
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
render.highlight('down')
|
|
784
|
+
|
|
785
|
+
expect(render.getOptions()[0].classList.contains(render.classes.highlighted)).toBe(true)
|
|
786
|
+
})
|
|
787
|
+
|
|
788
|
+
test('select last option with up when no option is highlighted or selected', () => {
|
|
789
|
+
render.renderOptions(
|
|
790
|
+
render.store.partialToFullData([
|
|
791
|
+
{
|
|
792
|
+
text: 'opt0'
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
text: 'opt1'
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
text: 'opt2'
|
|
799
|
+
}
|
|
800
|
+
])
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
render.highlight('up')
|
|
804
|
+
|
|
805
|
+
expect(render.getOptions()[2].classList.contains(render.classes.highlighted)).toBe(true)
|
|
806
|
+
})
|
|
807
|
+
|
|
808
|
+
test('highlight next option on down after highlighted option', () => {
|
|
809
|
+
render.renderOptions(
|
|
810
|
+
render.store.partialToFullData([
|
|
811
|
+
{
|
|
812
|
+
text: 'opt0',
|
|
813
|
+
class: render.classes.highlighted
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
text: 'opt1'
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
text: 'opt2'
|
|
820
|
+
}
|
|
821
|
+
])
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
render.highlight('down')
|
|
825
|
+
|
|
826
|
+
expect(render.getOptions()[1].classList.contains(render.classes.highlighted)).toBe(true)
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
test('highlight previous option on up before highlighted option', () => {
|
|
830
|
+
render.renderOptions(
|
|
831
|
+
render.store.partialToFullData([
|
|
832
|
+
{
|
|
833
|
+
text: 'opt0'
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
text: 'opt1'
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
text: 'opt2',
|
|
840
|
+
class: render.classes.highlighted
|
|
841
|
+
}
|
|
842
|
+
])
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
render.highlight('up')
|
|
846
|
+
|
|
847
|
+
expect(render.getOptions()[1].classList.contains(render.classes.highlighted)).toBe(true)
|
|
848
|
+
})
|
|
849
|
+
|
|
850
|
+
test('highlight next option on down after selected option when no options is highlighted', () => {
|
|
851
|
+
render.renderOptions(
|
|
852
|
+
render.store.partialToFullData([
|
|
853
|
+
{
|
|
854
|
+
text: 'opt0',
|
|
855
|
+
selected: true
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
text: 'opt1'
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
text: 'opt2'
|
|
862
|
+
}
|
|
863
|
+
])
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
render.highlight('down')
|
|
867
|
+
|
|
868
|
+
expect(render.getOptions()[1].classList.contains(render.classes.highlighted)).toBe(true)
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
test('skip to last option when using up at the first option', () => {
|
|
872
|
+
render.renderOptions(
|
|
873
|
+
render.store.partialToFullData([
|
|
874
|
+
{
|
|
875
|
+
text: 'opt0',
|
|
876
|
+
selected: true
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
text: 'opt1'
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
text: 'opt2'
|
|
883
|
+
}
|
|
884
|
+
])
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
render.highlight('up')
|
|
888
|
+
|
|
889
|
+
expect(render.getOptions()[2].classList.contains(render.classes.highlighted)).toBe(true)
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
test('highlight next option within opt group on down', () => {
|
|
893
|
+
render.renderOptions(
|
|
894
|
+
render.store.partialToFullData([
|
|
895
|
+
{
|
|
896
|
+
text: 'opt0',
|
|
897
|
+
selected: true
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
label: 'opt group',
|
|
901
|
+
options: [
|
|
902
|
+
{
|
|
903
|
+
text: 'opt1'
|
|
904
|
+
}
|
|
905
|
+
]
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
text: 'opt2'
|
|
909
|
+
}
|
|
910
|
+
])
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
render.highlight('down')
|
|
914
|
+
|
|
915
|
+
expect(render.getOptions()[1].classList.contains(render.classes.highlighted)).toBe(true)
|
|
916
|
+
})
|
|
917
|
+
|
|
918
|
+
test('highlight previous option within opt group on up', () => {
|
|
919
|
+
render.renderOptions(
|
|
920
|
+
render.store.partialToFullData([
|
|
921
|
+
{
|
|
922
|
+
text: 'opt0'
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
label: 'opt group',
|
|
926
|
+
options: [
|
|
927
|
+
{
|
|
928
|
+
text: 'opt1',
|
|
929
|
+
selected: true
|
|
930
|
+
}
|
|
931
|
+
]
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
text: 'opt2'
|
|
935
|
+
}
|
|
936
|
+
])
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
render.highlight('up')
|
|
940
|
+
|
|
941
|
+
expect(render.getOptions()[0].classList.contains(render.classes.highlighted)).toBe(true)
|
|
942
|
+
})
|
|
943
|
+
})
|
|
944
|
+
|
|
945
|
+
describe('listDiv', () => {
|
|
946
|
+
test('list div has correct class', () => {
|
|
947
|
+
const list = render.listDiv()
|
|
948
|
+
|
|
949
|
+
expect(list.classList.contains(render.classes.list)).toBe(true)
|
|
950
|
+
})
|
|
951
|
+
})
|
|
952
|
+
|
|
953
|
+
describe('renderError', () => {
|
|
954
|
+
test('error message is rendered correctly', () => {
|
|
955
|
+
expect(render.content.list.children).toHaveLength(0)
|
|
956
|
+
|
|
957
|
+
render.renderError('test error')
|
|
958
|
+
|
|
959
|
+
expect(render.content.list.children).toHaveLength(1)
|
|
960
|
+
expect(render.content.list.children.item(0)).toBeInstanceOf(HTMLDivElement)
|
|
961
|
+
expect(render.content.list.children.item(0)?.className).toBe(render.classes.error)
|
|
962
|
+
expect(render.content.list.children.item(0)?.textContent).toBe('test error')
|
|
963
|
+
})
|
|
964
|
+
|
|
965
|
+
test('list is reset on new error', () => {
|
|
966
|
+
expect(render.content.list.children).toHaveLength(0)
|
|
967
|
+
|
|
968
|
+
render.renderError('test error')
|
|
969
|
+
expect(render.content.list.children).toHaveLength(1)
|
|
970
|
+
|
|
971
|
+
render.renderError('error 2')
|
|
972
|
+
expect(render.content.list.children).toHaveLength(1)
|
|
973
|
+
expect(render.content.list.children.item(0)?.textContent).toBe('error 2')
|
|
974
|
+
})
|
|
975
|
+
})
|
|
976
|
+
|
|
977
|
+
describe('renderSearching', () => {
|
|
978
|
+
test('search text is rendered correctly', () => {
|
|
979
|
+
expect(render.content.list.children).toHaveLength(0)
|
|
980
|
+
|
|
981
|
+
render.settings.searchingText = 'search'
|
|
982
|
+
render.renderSearching()
|
|
983
|
+
|
|
984
|
+
expect(render.content.list.children).toHaveLength(1)
|
|
985
|
+
expect(render.content.list.children.item(0)).toBeInstanceOf(HTMLDivElement)
|
|
986
|
+
expect(render.content.list.children.item(0)?.className).toBe(render.classes.searching)
|
|
987
|
+
expect(render.content.list.children.item(0)?.textContent).toBe('search')
|
|
988
|
+
})
|
|
989
|
+
|
|
990
|
+
test('list is reset on new search text', () => {
|
|
991
|
+
expect(render.content.list.children).toHaveLength(0)
|
|
992
|
+
|
|
993
|
+
render.settings.searchingText = 'search'
|
|
994
|
+
render.renderSearching()
|
|
995
|
+
expect(render.content.list.children).toHaveLength(1)
|
|
996
|
+
|
|
997
|
+
render.settings.searchingText = 'search 2'
|
|
998
|
+
render.renderSearching()
|
|
999
|
+
expect(render.content.list.children).toHaveLength(1)
|
|
1000
|
+
expect(render.content.list.children.item(0)?.textContent).toBe('search 2')
|
|
1001
|
+
})
|
|
1002
|
+
})
|
|
1003
|
+
|
|
1004
|
+
describe('renderOptions', () => {})
|
|
1005
|
+
|
|
1006
|
+
describe('option', () => {
|
|
1007
|
+
test('add inline styles correctly', () => {
|
|
1008
|
+
const option = render.option(
|
|
1009
|
+
new Option({
|
|
1010
|
+
text: 'opt',
|
|
1011
|
+
style: 'color: red'
|
|
1012
|
+
})
|
|
1013
|
+
)
|
|
1014
|
+
|
|
1015
|
+
expect(option.style.color).toBe('red')
|
|
1016
|
+
})
|
|
1017
|
+
|
|
1018
|
+
test('add hidden class on option with display false', () => {
|
|
1019
|
+
const option = render.option(
|
|
1020
|
+
new Option({
|
|
1021
|
+
text: 'opt',
|
|
1022
|
+
display: false
|
|
1023
|
+
})
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
expect(option.classList.contains(render.classes.hide)).toBe(true)
|
|
1027
|
+
})
|
|
1028
|
+
|
|
1029
|
+
test('add hidden class on selected option when hideSelected setting is true', () => {
|
|
1030
|
+
render.settings.hideSelected = true
|
|
1031
|
+
|
|
1032
|
+
const option = render.option(
|
|
1033
|
+
new Option({
|
|
1034
|
+
text: 'opt',
|
|
1035
|
+
selected: true
|
|
1036
|
+
})
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
expect(option.classList.contains(render.classes.hide)).toBe(true)
|
|
1040
|
+
})
|
|
1041
|
+
|
|
1042
|
+
test('title attribute is set when showOptionTooltips setting is true', () => {
|
|
1043
|
+
render.settings.showOptionTooltips = true
|
|
1044
|
+
|
|
1045
|
+
const option = render.option(
|
|
1046
|
+
new Option({
|
|
1047
|
+
text: 'opt'
|
|
1048
|
+
})
|
|
1049
|
+
)
|
|
1050
|
+
|
|
1051
|
+
expect(option.getAttribute('title')).toBe('opt')
|
|
1052
|
+
})
|
|
1053
|
+
|
|
1054
|
+
test('text is highlighted correctly with option text', () => {
|
|
1055
|
+
render.settings.searchHighlight = true
|
|
1056
|
+
render.content.search.input.value = 'opt'
|
|
1057
|
+
|
|
1058
|
+
const option = render.option(
|
|
1059
|
+
new Option({
|
|
1060
|
+
text: 'opt 1'
|
|
1061
|
+
})
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
expect(option.querySelector('mark')).toBeTruthy()
|
|
1065
|
+
expect(option.querySelector('mark')?.textContent).toBe('opt')
|
|
1066
|
+
})
|
|
1067
|
+
|
|
1068
|
+
test('text is highlighted correctly with option HTML', () => {
|
|
1069
|
+
render.settings.searchHighlight = true
|
|
1070
|
+
render.content.search.input.value = 'opt'
|
|
1071
|
+
|
|
1072
|
+
const option = render.option(
|
|
1073
|
+
new Option({
|
|
1074
|
+
text: 'opt 1',
|
|
1075
|
+
html: '<h1>opt 1</h1>'
|
|
1076
|
+
})
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
expect(option.querySelector('mark')).toBeTruthy()
|
|
1080
|
+
expect(option.querySelector('mark')?.textContent).toBe('opt')
|
|
1081
|
+
})
|
|
1082
|
+
|
|
1083
|
+
test('click does nothing when option is disabled', () => {
|
|
1084
|
+
const option = render.option(
|
|
1085
|
+
new Option({
|
|
1086
|
+
text: 'opt 1',
|
|
1087
|
+
disabled: true
|
|
1088
|
+
})
|
|
1089
|
+
)
|
|
1090
|
+
option.dispatchEvent(new MouseEvent('click'))
|
|
1091
|
+
|
|
1092
|
+
expect(addOptionMock).not.toHaveBeenCalled()
|
|
1093
|
+
expect(setSelectedMock).not.toHaveBeenCalled()
|
|
1094
|
+
})
|
|
1095
|
+
|
|
1096
|
+
test('click does nothing when max count of selected options is reached', () => {
|
|
1097
|
+
render.settings.isMultiple = true
|
|
1098
|
+
render.settings.maxSelected = 1
|
|
1099
|
+
|
|
1100
|
+
const option = render.option(
|
|
1101
|
+
new Option({
|
|
1102
|
+
text: 'opt 1'
|
|
1103
|
+
})
|
|
1104
|
+
)
|
|
1105
|
+
option.dispatchEvent(new MouseEvent('click'))
|
|
1106
|
+
|
|
1107
|
+
expect(addOptionMock).not.toHaveBeenCalled()
|
|
1108
|
+
expect(setSelectedMock).not.toHaveBeenCalled()
|
|
1109
|
+
})
|
|
1110
|
+
|
|
1111
|
+
test('click does nothing when min count of selected options is reached', () => {
|
|
1112
|
+
render.settings.isMultiple = true
|
|
1113
|
+
render.settings.allowDeselect = true
|
|
1114
|
+
render.settings.minSelected = 1
|
|
1115
|
+
|
|
1116
|
+
const option = render.option(
|
|
1117
|
+
new Option({
|
|
1118
|
+
text: 'opt 1',
|
|
1119
|
+
selected: true
|
|
1120
|
+
})
|
|
1121
|
+
)
|
|
1122
|
+
option.dispatchEvent(new MouseEvent('click'))
|
|
1123
|
+
|
|
1124
|
+
expect(addOptionMock).not.toHaveBeenCalled()
|
|
1125
|
+
expect(setSelectedMock).not.toHaveBeenCalled()
|
|
1126
|
+
})
|
|
1127
|
+
|
|
1128
|
+
test('click removes option', () => {
|
|
1129
|
+
const option = render.option(
|
|
1130
|
+
new Option({
|
|
1131
|
+
text: 'new opt 1'
|
|
1132
|
+
})
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
option.dispatchEvent(new MouseEvent('click'))
|
|
1136
|
+
|
|
1137
|
+
expect(addOptionMock).toHaveBeenCalled()
|
|
1138
|
+
|
|
1139
|
+
// check that we add the right option
|
|
1140
|
+
expect(addOptionMock.mock.calls[0][0].text).toBe('new opt 1')
|
|
1141
|
+
expect(setSelectedMock).toHaveBeenCalled()
|
|
1142
|
+
})
|
|
1143
|
+
})
|
|
1144
|
+
|
|
1145
|
+
describe('destroy', () => {
|
|
1146
|
+
test('elements get removed', () => {
|
|
1147
|
+
expect(render.main.main).toBeInstanceOf(HTMLDivElement)
|
|
1148
|
+
expect(render.content.main).toBeInstanceOf(HTMLDivElement)
|
|
1149
|
+
|
|
1150
|
+
// set some simple IDs, so we can search for the elements in the DOM
|
|
1151
|
+
render.main.main.id = 'main-id'
|
|
1152
|
+
render.content.main.id = 'content-id'
|
|
1153
|
+
|
|
1154
|
+
render.destroy()
|
|
1155
|
+
|
|
1156
|
+
expect(document.getElementById('main-id')).toBeNull()
|
|
1157
|
+
expect(document.getElementById('#content-id')).toBeNull()
|
|
1158
|
+
})
|
|
1159
|
+
})
|
|
1160
|
+
|
|
1161
|
+
describe('moveContentAbove', () => {
|
|
1162
|
+
test('correct classes are set', () => {
|
|
1163
|
+
render.moveContentAbove()
|
|
1164
|
+
|
|
1165
|
+
expect(render.main.main.classList.contains(render.classes.openAbove)).toBe(true)
|
|
1166
|
+
expect(render.main.main.classList.contains(render.classes.openBelow)).toBe(false)
|
|
1167
|
+
|
|
1168
|
+
expect(render.content.main.classList.contains(render.classes.openAbove)).toBe(true)
|
|
1169
|
+
expect(render.content.main.classList.contains(render.classes.openBelow)).toBe(false)
|
|
1170
|
+
})
|
|
1171
|
+
})
|
|
1172
|
+
|
|
1173
|
+
describe('moveContentBelow', () => {
|
|
1174
|
+
test('correct classes are set', () => {
|
|
1175
|
+
render.moveContentBelow()
|
|
1176
|
+
|
|
1177
|
+
expect(render.main.main.classList.contains(render.classes.openAbove)).toBe(false)
|
|
1178
|
+
expect(render.main.main.classList.contains(render.classes.openBelow)).toBe(true)
|
|
1179
|
+
|
|
1180
|
+
expect(render.content.main.classList.contains(render.classes.openAbove)).toBe(false)
|
|
1181
|
+
expect(render.content.main.classList.contains(render.classes.openBelow)).toBe(true)
|
|
1182
|
+
})
|
|
1183
|
+
})
|
|
1184
|
+
|
|
1185
|
+
describe('ensureElementInView', () => {})
|
|
1186
|
+
|
|
1187
|
+
describe('putContent', () => {})
|
|
1188
|
+
|
|
1189
|
+
describe('updateDeselectAll', () => {})
|
|
39
1190
|
})
|