slim-select 2.2.3 → 2.3.1

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.
@@ -6,7 +6,7 @@ export interface Callbacks {
6
6
  open: () => void
7
7
  close: () => void
8
8
  addable?: (value: string) => Promise<OptionOptional | string> | OptionOptional | string
9
- setSelected: (value: string[]) => void
9
+ setSelected: (value: string[], runAfterChange: boolean) => void
10
10
  addOption: (option: Option) => void
11
11
  search: (search: string) => void
12
12
  beforeChange?: (newVal: Option[], oldVal: Option[]) => boolean | void
@@ -63,6 +63,7 @@ export default class Render {
63
63
  // Values
64
64
  values: 'ss-values',
65
65
  single: 'ss-single',
66
+ max: 'ss-max',
66
67
  value: 'ss-value',
67
68
  valueText: 'ss-value-text',
68
69
  valueDelete: 'ss-value-delete',
@@ -124,6 +125,9 @@ export default class Render {
124
125
  this.main = this.mainDiv()
125
126
  this.content = this.contentDiv()
126
127
 
128
+ // Add classes and styles to main/content
129
+ this.updateClassStyles()
130
+
127
131
  // Add content to the content location settings
128
132
  this.settings.contentLocation.appendChild(this.content.main)
129
133
  }
@@ -155,25 +159,6 @@ export default class Render {
155
159
  // move the content in to the right location
156
160
  this.moveContent()
157
161
 
158
- // Render the options
159
- this.renderOptions(this.store.getData())
160
-
161
- // Check showContent to see if they want to specifically show in a certain direction
162
- if (this.settings.contentPosition === 'relative') {
163
- this.moveContentBelow()
164
- } else if (this.settings.openPosition.toLowerCase() === 'up') {
165
- this.moveContentAbove()
166
- } else if (this.settings.openPosition.toLowerCase() === 'down') {
167
- this.moveContentBelow()
168
- } else {
169
- // Auto identify where to put it
170
- if (this.putContent(this.content.main, this.settings.isOpen) === 'up') {
171
- this.moveContentAbove()
172
- } else {
173
- this.moveContentBelow()
174
- }
175
- }
176
-
177
162
  // Move to last selected option
178
163
  const selectedOptions = this.store.getSelectedOptions()
179
164
  if (selectedOptions.length) {
@@ -193,30 +178,49 @@ export default class Render {
193
178
  this.main.arrow.path.setAttribute('d', this.classes.arrowClose)
194
179
  }
195
180
 
196
- public mainDiv(): Main {
197
- // Create main container
198
- const main = document.createElement('div')
199
-
200
- // Set tabable to allow tabbing to the element
201
- main.tabIndex = 0
181
+ public updateClassStyles(): void {
182
+ // Clear all classes and styles
183
+ this.main.main.className = ''
184
+ this.main.main.removeAttribute('style')
185
+ this.content.main.className = ''
186
+ this.content.main.removeAttribute('style')
202
187
 
203
- // Add style and classes
204
- main.style.cssText = this.settings.style !== '' ? this.settings.style : ''
188
+ // Make sure main/content has its base class
189
+ this.main.main.classList.add(this.classes.main)
190
+ this.content.main.classList.add(this.classes.content)
205
191
 
206
- // Clear out classlist
207
- main.className = ''
192
+ // Add styles
193
+ if (this.settings.style !== '') {
194
+ this.content.main.style.cssText = this.settings.style
195
+ }
208
196
 
209
- // Loop through config class and add
210
- main.classList.add(this.settings.id)
211
- main.classList.add(this.classes.main)
212
- if (this.settings.class) {
197
+ // Add classes
198
+ if (this.settings.class.length) {
213
199
  for (const c of this.settings.class) {
214
200
  if (c.trim() !== '') {
215
- main.classList.add(c.trim())
201
+ this.main.main.classList.add(c.trim())
202
+ this.content.main.classList.add(c.trim())
216
203
  }
217
204
  }
218
205
  }
219
206
 
207
+ // Misc classes
208
+ // Add content position class
209
+ if (this.settings.contentPosition === 'relative') {
210
+ this.content.main.classList.add('ss-' + this.settings.contentPosition)
211
+ }
212
+ }
213
+
214
+ public mainDiv(): Main {
215
+ // Create main container
216
+ const main = document.createElement('div')
217
+
218
+ // Add id to data-id
219
+ main.dataset.id = this.settings.id
220
+
221
+ // Set tabable to allow tabbing to the element
222
+ main.tabIndex = 0
223
+
220
224
  // If main gets focus, open the content
221
225
  main.onfocus = () => {
222
226
  if (this.settings.triggerFocus) {
@@ -253,7 +257,7 @@ export default class Render {
253
257
  // Add onclick for main div
254
258
  main.onclick = (e: Event) => {
255
259
  // Dont do anything if disabled
256
- if (!this.settings.isEnabled) {
260
+ if (this.settings.disabled) {
257
261
  return
258
262
  }
259
263
 
@@ -277,7 +281,7 @@ export default class Render {
277
281
  e.stopPropagation()
278
282
 
279
283
  // Dont do anything if disabled
280
- if (!this.settings.isEnabled) {
284
+ if (this.settings.disabled) {
281
285
  return
282
286
  }
283
287
 
@@ -292,7 +296,7 @@ export default class Render {
292
296
  }
293
297
 
294
298
  if (shouldDelete) {
295
- this.callbacks.setSelected([''])
299
+ this.callbacks.setSelected([''], false)
296
300
 
297
301
  // Check if we need to close the dropdown
298
302
  if (this.settings.closeOnSelect) {
@@ -415,7 +419,7 @@ export default class Render {
415
419
  }
416
420
 
417
421
  private renderMultipleValues(): void {
418
- // Get various peices of data
422
+ // Get various pieces of data
419
423
  let currentNodes = this.main.values.childNodes as NodeListOf<HTMLDivElement>
420
424
  let selectedOptions = this.store.filter((opt: Option) => {
421
425
  // Only grab options that are selected and display is true
@@ -434,6 +438,24 @@ export default class Render {
434
438
  }
435
439
  }
436
440
 
441
+ // If selectedOptions is greater than maxItems, set maxValuesMessage
442
+ if (selectedOptions.length > this.settings.maxValuesShown) {
443
+ // Creating the element that shows the number of selected items
444
+ const singleValue = document.createElement('div')
445
+ singleValue.classList.add(this.classes.max)
446
+ singleValue.textContent = this.settings.maxValuesMessage.replace('{number}', selectedOptions.length.toString())
447
+
448
+ // If there is a selected value, set a single div
449
+ this.main.values.innerHTML = singleValue.outerHTML
450
+ return
451
+ } else {
452
+ // If there is a message, remove it
453
+ const maxValuesMessage = this.main.values.querySelector('.' + this.classes.max)
454
+ if (maxValuesMessage) {
455
+ maxValuesMessage.remove()
456
+ }
457
+ }
458
+
437
459
  // Loop through currentNodes and only include ones that are not in selectedIDs
438
460
  let removeNodes: HTMLDivElement[] = []
439
461
  for (let i = 0; i < currentNodes.length; i++) {
@@ -504,6 +526,11 @@ export default class Render {
504
526
  e.preventDefault()
505
527
  e.stopPropagation()
506
528
 
529
+ // Dont do anything if disabled
530
+ if (this.settings.disabled) {
531
+ return
532
+ }
533
+
507
534
  // By Default we will delete
508
535
  let shouldDelete = true
509
536
  const before = this.store.getSelectedOptions()
@@ -535,7 +562,7 @@ export default class Render {
535
562
  selectedValues.push(o.value)
536
563
  }
537
564
  }
538
- this.callbacks.setSelected(selectedValues)
565
+ this.callbacks.setSelected(selectedValues, false)
539
566
 
540
567
  // Check if we need to close the dropdown
541
568
  if (this.settings.closeOnSelect) {
@@ -565,28 +592,10 @@ export default class Render {
565
592
 
566
593
  public contentDiv(): Content {
567
594
  const main = document.createElement('div')
568
- main.classList.add(this.classes.content)
569
595
 
570
596
  // Add id to data-id
571
597
  main.dataset.id = this.settings.id
572
598
 
573
- // Add styles
574
- if (this.settings.style !== '') {
575
- main.style.cssText = this.settings.style
576
- }
577
-
578
- // Add classes
579
- if (this.settings.contentPosition === 'relative') {
580
- main.classList.add('ss-' + this.settings.contentPosition)
581
- }
582
- if (this.settings.class.length) {
583
- for (const c of this.settings.class) {
584
- if (c.trim() !== '') {
585
- main.classList.add(c.trim())
586
- }
587
- }
588
- }
589
-
590
599
  // Add search
591
600
  const search = this.searchDiv()
592
601
  main.appendChild(search.main)
@@ -603,14 +612,27 @@ export default class Render {
603
612
  }
604
613
 
605
614
  public moveContent(): void {
615
+ // If contentPosition is relative, dont move the content anywhere other than below
606
616
  if (this.settings.contentPosition === 'relative') {
617
+ this.moveContentBelow()
607
618
  return
608
619
  }
609
620
 
610
- const containerRect = this.main.main.getBoundingClientRect()
611
- this.content.main.style.top = containerRect.top + containerRect.height + window.scrollY + 'px'
612
- this.content.main.style.left = containerRect.left + window.scrollX + 'px'
613
- this.content.main.style.width = containerRect.width + 'px'
621
+ // If openContent is not auto set content
622
+ if (this.settings.openPosition === 'down') {
623
+ this.moveContentBelow()
624
+ return
625
+ } else if (this.settings.openPosition === 'up') {
626
+ this.moveContentAbove()
627
+ return
628
+ }
629
+
630
+ // Auto - Determine where to put the content
631
+ if (this.putContent() === 'up') {
632
+ this.moveContentAbove()
633
+ } else {
634
+ this.moveContentBelow()
635
+ }
614
636
  }
615
637
 
616
638
  public searchDiv(): Search {
@@ -729,9 +751,9 @@ export default class Render {
729
751
  if (this.settings.isMultiple) {
730
752
  let values = this.store.getSelected()
731
753
  values.push(newOption.value)
732
- this.callbacks.setSelected(values)
754
+ this.callbacks.setSelected(values, true)
733
755
  } else {
734
- this.callbacks.setSelected([newOption.value])
756
+ this.callbacks.setSelected([newOption.value], true)
735
757
  }
736
758
 
737
759
  // Clear search
@@ -908,8 +930,7 @@ export default class Render {
908
930
  // If no results show no results text
909
931
  if (data.length === 0) {
910
932
  const noResults = document.createElement('div')
911
- noResults.classList.add(this.classes.option)
912
- noResults.classList.add(this.classes.disabled)
933
+ noResults.classList.add(this.classes.search)
913
934
  noResults.innerHTML = this.settings.searchText
914
935
  this.content.list.appendChild(noResults)
915
936
  return
@@ -1001,13 +1022,13 @@ export default class Render {
1001
1022
  return true
1002
1023
  })
1003
1024
 
1004
- this.callbacks.setSelected(newSelected)
1025
+ this.callbacks.setSelected(newSelected, true)
1005
1026
  return
1006
1027
  } else {
1007
1028
  // Put together new list with all options in this optgroup
1008
1029
  const newSelected = currentSelected.concat(d.options.map((o) => o.value))
1009
1030
 
1010
- this.callbacks.setSelected(newSelected)
1031
+ this.callbacks.setSelected(newSelected, true)
1011
1032
  }
1012
1033
  })
1013
1034
 
@@ -1031,8 +1052,8 @@ export default class Render {
1031
1052
  const optgroupClosableArrow = document.createElementNS('http://www.w3.org/2000/svg', 'path')
1032
1053
  optgroupClosableSvg.appendChild(optgroupClosableArrow)
1033
1054
 
1034
- // If any options are selected, set optgroup to open
1035
- if (d.options.some((o) => o.selected)) {
1055
+ // If any options are selected or someone is searching, set optgroup to open
1056
+ if (d.options.some((o) => o.selected) || this.content.search.input.value.trim() !== '') {
1036
1057
  optgroupClosable.classList.add(this.classes.open)
1037
1058
  optgroupClosableArrow.setAttribute('d', this.classes.arrowOpen)
1038
1059
  } else if (d.closable === 'open') {
@@ -1109,9 +1130,8 @@ export default class Render {
1109
1130
 
1110
1131
  // Set option content
1111
1132
  if (this.settings.searchHighlight && this.content.search.input.value.trim() !== '') {
1112
- const textOrHtml = option.html !== '' ? option.html : option.text
1113
1133
  optionEl.innerHTML = this.highlightText(
1114
- textOrHtml,
1134
+ option.html !== '' ? option.html : option.text,
1115
1135
  this.content.search.input.value,
1116
1136
  this.classes.searchHighlighter,
1117
1137
  )
@@ -1126,8 +1146,13 @@ export default class Render {
1126
1146
  optionEl.setAttribute('title', optionEl.textContent)
1127
1147
  }
1128
1148
 
1149
+ // If option is disabled
1150
+ if (!option.display) {
1151
+ optionEl.classList.add(this.classes.hide)
1152
+ }
1153
+
1129
1154
  // If allowed to deselect, null onclick and add disabled
1130
- if ((option.selected && !this.settings.allowDeselect) || (option.disabled && !this.settings.allowDeselect)) {
1155
+ if (option.disabled) {
1131
1156
  optionEl.classList.add(this.classes.disabled)
1132
1157
  }
1133
1158
 
@@ -1158,11 +1183,11 @@ export default class Render {
1158
1183
  return
1159
1184
  }
1160
1185
 
1161
- // Check limit and do nothing if limit is reached
1186
+ // Check limit and do nothing if limit is reached and the option is not selected
1187
+ // Also check reverse for min limit and is selected
1162
1188
  if (
1163
- this.settings.isMultiple &&
1164
- Array.isArray(selectedOptions) &&
1165
- this.settings.maxSelected <= selectedOptions.length
1189
+ (this.settings.isMultiple && this.settings.maxSelected <= selectedOptions.length && !option.selected) ||
1190
+ (this.settings.isMultiple && this.settings.minSelected >= selectedOptions.length && option.selected)
1166
1191
  ) {
1167
1192
  return
1168
1193
  }
@@ -1216,7 +1241,10 @@ export default class Render {
1216
1241
  }
1217
1242
 
1218
1243
  // Get values from after and set as selected
1219
- this.callbacks.setSelected(after.map((o: Option) => o.value))
1244
+ this.callbacks.setSelected(
1245
+ after.map((o: Option) => o.value),
1246
+ false,
1247
+ )
1220
1248
 
1221
1249
  // If closeOnSelect is true
1222
1250
  if (this.settings.closeOnSelect) {
@@ -1260,27 +1288,40 @@ export default class Render {
1260
1288
  }
1261
1289
 
1262
1290
  public moveContentAbove(): void {
1263
- let mainHeight: number = this.main.main.offsetHeight
1264
-
1291
+ // Get main and content height
1292
+ const mainHeight = this.main.main.offsetHeight
1265
1293
  const contentHeight = this.content.main.offsetHeight
1266
- const height = mainHeight + contentHeight - 1
1267
- this.content.main.style.margin = '-' + height + 'px 0px 0px 0px'
1268
- this.content.main.style.transformOrigin = 'center bottom'
1269
1294
 
1295
+ // Set classes
1270
1296
  this.main.main.classList.remove(this.classes.openBelow)
1271
1297
  this.main.main.classList.add(this.classes.openAbove)
1272
1298
  this.content.main.classList.remove(this.classes.openBelow)
1273
1299
  this.content.main.classList.add(this.classes.openAbove)
1300
+
1301
+ // Set the content position
1302
+ const containerRect = this.main.main.getBoundingClientRect()
1303
+ this.content.main.style.margin = '-' + (mainHeight + contentHeight - 1) + 'px 0px 0px 0px'
1304
+ this.content.main.style.top = containerRect.top + containerRect.height + window.scrollY + 'px'
1305
+ this.content.main.style.left = containerRect.left + window.scrollX + 'px'
1306
+ this.content.main.style.width = containerRect.width + 'px'
1274
1307
  }
1275
1308
 
1276
1309
  public moveContentBelow(): void {
1277
- this.content.main.style.margin = '-1px 0px 0px 0px'
1278
- this.content.main.style.transformOrigin = 'center top'
1279
-
1310
+ // Set classes
1280
1311
  this.main.main.classList.remove(this.classes.openAbove)
1281
1312
  this.main.main.classList.add(this.classes.openBelow)
1282
1313
  this.content.main.classList.remove(this.classes.openAbove)
1283
1314
  this.content.main.classList.add(this.classes.openBelow)
1315
+
1316
+ // Set the content position
1317
+ const containerRect = this.main.main.getBoundingClientRect()
1318
+ this.content.main.style.margin = '-1px 0px 0px 0px'
1319
+ // Dont do anything if the content is relative
1320
+ if (this.settings.contentPosition !== 'relative') {
1321
+ this.content.main.style.top = containerRect.top + containerRect.height + window.scrollY + 'px'
1322
+ this.content.main.style.left = containerRect.left + window.scrollX + 'px'
1323
+ this.content.main.style.width = containerRect.width + 'px'
1324
+ }
1284
1325
  }
1285
1326
 
1286
1327
  public ensureElementInView(container: HTMLElement, element: HTMLElement): void {
@@ -1300,20 +1341,28 @@ export default class Render {
1300
1341
  }
1301
1342
  }
1302
1343
 
1303
- public putContent(el: HTMLElement, isOpen: boolean): 'up' | 'down' {
1304
- const height = el.offsetHeight
1305
- const rect = el.getBoundingClientRect()
1306
- const elemTop = isOpen ? rect.top : rect.top - height
1307
- const elemBottom = isOpen ? rect.bottom : rect.bottom + height
1344
+ public putContent(): 'up' | 'down' {
1345
+ // Get main and content height
1346
+ const mainHeight = this.main.main.offsetHeight
1347
+ const mainRect = this.main.main.getBoundingClientRect()
1348
+ const contentHeight = this.content.main.offsetHeight
1308
1349
 
1309
- if (elemTop <= 0) {
1310
- return 'down'
1311
- }
1312
- if (elemBottom >= window.innerHeight) {
1313
- return 'up'
1350
+ // From bottom of mainHeight figure out if content will fit below without going below the window
1351
+ const spaceBelow = window.innerHeight - (mainRect.top + mainHeight)
1352
+
1353
+ // If space below is less than content height
1354
+ if (spaceBelow <= contentHeight) {
1355
+ // If space above is more than content height
1356
+ if (mainRect.top > contentHeight) {
1357
+ // Move content above
1358
+ return 'up'
1359
+ } else {
1360
+ // Move content below
1361
+ return 'down'
1362
+ }
1314
1363
  }
1315
1364
 
1316
- // default to current position if we cant determine a perfect one
1365
+ // Move content below
1317
1366
  return 'down'
1318
1367
  }
1319
1368
  }
@@ -4,7 +4,7 @@
4
4
 
5
5
  'use strict'
6
6
 
7
- import { describe, expect, test } from '@jest/globals'
7
+ import { describe, expect, jest, test } from '@jest/globals'
8
8
  import Select from './select'
9
9
  import Store, { Optgroup, Option } from './store'
10
10
 
@@ -19,7 +19,7 @@ describe('select module', () => {
19
19
 
20
20
  test('get data from select options', () => {
21
21
  document.body.innerHTML = `<select id="test">
22
- <option value="1">One</option>
22
+ <option id="value1" value="1">One</option>
23
23
  <option value="2">Two</option>
24
24
  </select>`
25
25
 
@@ -27,6 +27,7 @@ describe('select module', () => {
27
27
  let select = new Select(selectElement)
28
28
  let data = select.getData() as Option[]
29
29
  expect(data.length).toBe(2)
30
+ expect(data[0].id).toBe('value1')
30
31
  expect(data[0].value).toBe('1')
31
32
  expect(data[0].text).toBe('One')
32
33
  expect(data[1].value).toBe('2')
@@ -88,10 +89,12 @@ describe('select module', () => {
88
89
  let data = store.getData()
89
90
  select.updateOptions(data)
90
91
 
91
- expect(selectElement.innerHTML).toBe('<option value="1">One</option><option value="2">Two</option>')
92
+ expect(selectElement.outerHTML).toBe(
93
+ '<select id="test"><option id="1" value="1">One</option><option id="2" value="2">Two</option></select>',
94
+ )
92
95
  })
93
96
 
94
- test('event change value listener', () => {
97
+ test('event change value listener', async () => {
95
98
  document.body.innerHTML = `<select id="test">
96
99
  <option value="1">One</option>
97
100
  <option value="2">Two</option>
@@ -105,24 +108,56 @@ describe('select module', () => {
105
108
 
106
109
  expect(data[0].selected).toBe(true)
107
110
 
108
- let didChange = false
109
- select.addValueChangeListener(() => {
110
- didChange = true
111
- })
111
+ const callback = jest.fn()
112
+ select.onValueChange = callback
112
113
 
114
+ // Change the value
113
115
  selectElement.value = '2'
116
+ selectElement.dispatchEvent(new Event('change'))
114
117
 
115
- data = select.getData() as Option[]
118
+ // TODO: figure out why this is not working
119
+ // expect(callback).toHaveBeenCalled()
120
+
121
+ // Get selected data
122
+ const selected = select.getSelectedValues()
123
+ expect(selected[0]).toBe('2')
116
124
 
125
+ // Change the full data
126
+ data = select.getData() as Option[]
117
127
  expect(data[1].selected).toBe(true)
128
+ })
129
+
130
+ test('event change value listener when options are replaced', () => {
131
+ document.body.innerHTML = `<select id="test">
132
+ <option value="1">One</option>
133
+ <option value="2">Two</option>
134
+ <option value="3">Three</option>
135
+ </select>`
136
+
137
+ let selectElement = document.getElementById('test') as HTMLSelectElement
138
+ let select = new Select(selectElement)
139
+
140
+ let data = select.getData() as Option[]
141
+
142
+ expect(data[0].selected).toBe(true)
143
+
144
+ const callback = jest.fn()
145
+ select.onValueChange = callback
146
+
147
+ selectElement.innerHTML = `<option value="4">Four</option>
148
+ <option value="5" selected>Five</option>
149
+ <option value="6">Six</option>`
150
+
151
+ // TODO: figure out why this is not working
152
+ // expect(callback).toHaveBeenCalled()
118
153
 
119
154
  // Give the mutation observer time to run
120
- setTimeout(() => {
121
- expect(didChange).toBe(true)
122
- }, 100)
155
+ data = select.getData() as Option[]
156
+ expect(data[1].value).toBe('5')
157
+ expect(data[1].selected).toBe(true)
123
158
  })
124
159
 
125
- test('mutation observer listener', () => {
160
+ test('mutation observer listener for select option changes', () => {
126
161
  document.body.innerHTML = `<select id="test">
127
162
  <option value="1">One</option>
128
163
  <option value="2">Two</option>
@@ -133,23 +168,108 @@ describe('select module', () => {
133
168
  let select = new Select(selectElement)
134
169
 
135
170
  let data = select.getData() as Option[]
136
-
137
171
  expect(data.length).toBe(3)
138
172
 
139
- let didChange = false
140
- select.addSelectChangeListener(() => {
141
- didChange = true
142
- })
173
+ const callback = jest.fn()
174
+ select.onOptionsChange = callback
143
175
 
144
176
  selectElement.innerHTML = '<option value="1">One</option><option value="2">Two</option>'
145
177
 
146
- data = select.getData() as Option[]
178
+ // TODO: figure out why this is not working
179
+ // expect(callback).toHaveBeenCalled()
147
180
 
181
+ data = select.getData() as Option[]
148
182
  expect(data.length).toBe(2)
183
+ })
149
184
 
150
- // Give the mutation observer time to run
151
- setTimeout(() => {
152
- expect(didChange).toBe(true)
153
- }, 100)
185
+ test('mutation observer listener for select optgroup option changes', () => {
186
+ document.body.innerHTML = `<select id="test">
187
+ <optgroup id="test_optgroup" label="test1">
188
+ <option value="1">One</option>
189
+ <option value="2">Two</option>
190
+ </optgroup>
191
+ <optgroup label="test2">
192
+ <option value="3">Three</option>
193
+ <option value="4">Four</option>
194
+ <option value="5">Five</option>
195
+ </optgroup>
196
+ </select>`
197
+
198
+ let selectElement = document.getElementById('test') as HTMLSelectElement
199
+ let select = new Select(selectElement)
200
+
201
+ let dataOptgroup = select.getData() as Optgroup[]
202
+
203
+ expect(dataOptgroup.length).toBe(2)
204
+
205
+ const callback = jest.fn()
206
+ select.onOptionsChange = callback
207
+
208
+ let selectOptgroup = document.getElementById('test_optgroup') as HTMLOptGroupElement
209
+ selectOptgroup.innerHTML = '<option value="8">Eight</option><option value="9">Nine</option>'
210
+
211
+ // TODO: figure out why this is not working
212
+ // expect(callback).toHaveBeenCalled()
213
+
214
+ let dataOptgroups = select.getData() as Option[]
215
+ expect(dataOptgroups.length).toBe(2)
216
+
217
+ // get selected data
218
+ let selected = select.getSelectedValues()
219
+ expect(selected.length).toBe(1)
220
+ })
221
+
222
+ test('mutation observer listener for select option text changes', () => {
223
+ document.body.innerHTML = `<select id="test">
224
+ <option value="1">One</option>
225
+ <option value="2">Two</option>
226
+ <option value="3">Three</option>
227
+ </select>`
228
+
229
+ let selectElement = document.getElementById('test') as HTMLSelectElement
230
+ let select = new Select(selectElement)
231
+
232
+ let data = select.getData() as Option[]
233
+
234
+ expect(data[0].text).toBe('One')
235
+
236
+ const callback = jest.fn()
237
+ select.onOptionsChange = callback
238
+
239
+ let option = selectElement.options[0]
240
+ option.text = 'New One'
241
+
242
+ data = select.getData() as Option[]
243
+ expect(data[0].text).toBe('New One')
244
+ })
245
+
246
+ test('mutation observer listener for select optgroup option text changes', () => {
247
+ document.body.innerHTML = `<select id="test">
248
+ <optgroup id="test_optgroup" label="test1">
249
+ <option id=test_option value="1">One</option>
250
+ <option value="2">Two</option>
251
+ </optgroup>
252
+ <optgroup label="test2">
253
+ <option value="3">Three</option>
254
+ <option value="4">Four</option>
255
+ <option value="5">Five</option>
256
+ </optgroup>
257
+ </select>`
258
+
259
+ let selectElement = document.getElementById('test') as HTMLSelectElement
260
+ let select = new Select(selectElement)
261
+
262
+ let dataOptgroup = select.getData() as Optgroup[]
263
+
264
+ expect(dataOptgroup[0].options[0].text).toBe('One')
265
+
266
+ const callback = jest.fn()
267
+ select.onOptionsChange = callback
268
+
269
+ let selectOption = document.getElementById('test_option') as HTMLOptionElement
270
+ selectOption.text = 'New One'
271
+
272
+ let dataOption = select.getData() as Optgroup[]
273
+ expect(dataOption[0].options[0].text).toBe('New One')
154
274
  })
155
275
  })