bootstrap-input-spinner 4.1.0 → 4.1.2

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # bootstrap-input-spinner
2
2
 
3
- A Bootstrap extension to create input spinner elements for number input.
3
+ A Bootstrap 5 extension to create input spinner elements for number input.
4
4
 
5
- > Note: bootstrap-input-spinner is now a ES6 module. You find the old ES5 version in the folder `es5-deprecated`. The ES5 version is not maintained anymore and will be removed in the future.
5
+ > Note: bootstrap-input-spinner is now an ES6 module. The legacy ES5 version has been removed; if you still need it, pin to npm `3.x`.
6
6
 
7
7
  ![bootstrap-input-spinner](https://shaack.com/projekte/assets/img/bootstrap-input-spinner-floatingpoint-and-i18n.png)
8
8
  *Examples with floating-point and german localization*
@@ -72,7 +72,7 @@ Create the element in HTML. The attributes are compatible to the native `input[t
72
72
  </script>
73
73
  ```
74
74
 
75
- That's it. **No extra css needed**, just Bootstrap 5 and jQuery. (Note: jQuery will be removed in the future)
75
+ That's it. **No extra css needed**, just Bootstrap 5 and jQuery. jQuery is still a runtime dependency and is planned to be removed in a future release.
76
76
 
77
77
  ## API Reference
78
78
 
@@ -98,11 +98,13 @@ The InputSpinner also handles the standard input attributes `required`, `disable
98
98
 
99
99
  ### Create an instance in JavaScript
100
100
 
101
- Use JavaScript to create the instance as a jQuery plugin. You may provide additional configuration in an object as a
102
- config parameter.
101
+ Instantiate the `InputSpinner` class on any `<input type="number">` element. You may provide additional configuration
102
+ in an object as a second parameter.
103
103
 
104
104
  ```js
105
- $(element).inputSpinner(config);
105
+ import {InputSpinner} from "bootstrap-input-spinner/src/InputSpinner.js"
106
+
107
+ new InputSpinner(element, config)
106
108
  ```
107
109
 
108
110
  #### Configuration (props)
@@ -177,12 +179,20 @@ step.
177
179
  Used to format the number in the UI. Detected automatically from the user's browser, can be set to "de", "en",… or "
178
180
  de_DE", "en_GB",….
179
181
 
180
- ##### editor (*new!*)
182
+ ##### editor
183
+
184
+ An Editor defines how the input is parsed and rendered. The default editor is the internal `I18nEditor`, which
185
+ parses and renders an internationalized number. Additional editors live in `src/custom-editors.js` and are available
186
+ as named ES exports:
187
+
188
+ ```js
189
+ import {RawEditor, TimeEditor} from "bootstrap-input-spinner/src/custom-editors.js"
190
+
191
+ new InputSpinner(element, {editor: TimeEditor})
192
+ ```
181
193
 
182
- An Editor defines, how the input is parsed and rendered. The default editor of the spinner is the `I18nEditor`, which
183
- renders and parses an internationalized number value. There are custom editors in `/src/custom-editors.js`. An Editor
184
- must implement the two functions `parse(customValue)`, to parse the input to a number and `render(number)` to render the
185
- number to the spinner input.
194
+ An Editor must implement two functions: `parse(customFormat)` to turn the input string into a number, and
195
+ `render(number)` to format the number back for display.
186
196
 
187
197
  The simplest custom Editor is the `RawEditor`, it renders just the value und parses just the value, without any changes,
188
198
  like a native number input. It looks like this:
@@ -215,14 +225,19 @@ To modify the look completely, you can use the template parameter. There is an e
215
225
 
216
226
  ### Programmatic change and read of value
217
227
 
218
- To change or read the value just use the jQuery `val()` function on the input, like this
228
+ In vanilla JavaScript, read via `element.value` and write via `element.setValue(newValue)`:
219
229
 
220
230
  ```javascript
221
- var currentValue = $(element).val() // read
222
- $(element).val(newValue) // write
231
+ const currentValue = element.value // read
232
+ element.setValue(newValue) // write
223
233
  ```
224
234
 
225
- > **Hint:** Reading the value in vanilla JS with `element.value` will also work, but to set the value you have to use `element.setValue(newValue)` or `$(element).val(newValue)`
235
+ The jQuery `val()` function also works in both directions:
236
+
237
+ ```javascript
238
+ const currentValue = $(element).val() // read
239
+ $(element).val(newValue) // write
240
+ ```
226
241
 
227
242
  ### Handling attributes
228
243
 
@@ -257,14 +272,12 @@ $(element).on("change", function (event) {
257
272
 
258
273
  ### Methods
259
274
 
260
- Methods are passed as string values instead of the options object.
261
-
262
275
  #### destroy
263
276
 
264
277
  Removes the InputSpinner and shows the original input element.
265
278
 
266
279
  ```javascript
267
- $(element).inputSpinner("destroy")
280
+ element.destroyInputSpinner()
268
281
  ```
269
282
 
270
283
  ## Minified version
@@ -280,21 +293,27 @@ Just install uglify
280
293
  npm install uglify-js -g
281
294
  ```
282
295
 
283
- and then in the src-folder
296
+ and then in the `src` folder
284
297
 
285
298
  ```bash
286
- uglifyjs bootstrap-input-spinner.js --compress --mangle > bootstrap-input-spinner.min.js
299
+ uglifyjs InputSpinner.js --compress --mangle > InputSpinner.min.js
287
300
  ```
288
301
 
289
302
  Violà! :)
290
303
 
291
- ## Browser support
304
+ ## Testing
305
+
306
+ There is a [Teevi](https://github.com/shaack/teevi) based browser test suite under `test/`. Serve the repo with any
307
+ static server and open `test/index.html` in a browser to run it:
292
308
 
293
- The spinner works in all modern browsers and Internet Explorer. Not tested with IE < 11.
309
+ ```bash
310
+ npx http-server -p 8080
311
+ # then open http://localhost:8080/test/
312
+ ```
313
+
314
+ ## Browser support
294
315
 
295
- For older browsers (IE 9 or so), that doesn't support `Intl`, when you get an error message like
296
- **"Intl is not defined"** (See [issue #34](https://github.com/shaack/bootstrap-input-spinner/issues/34)), just use a
297
- shim or polyfill like [Intl.js](https://github.com/andyearnshaw/Intl.js), and it works.
316
+ The spinner works in all modern browsers that support ES modules.
298
317
 
299
318
  ---
300
319
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bootstrap-input-spinner",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
4
4
  "description": "A Bootstrap 5 / jQuery plugin to create input spinner elements for number input.",
5
5
  "browser": "./src/bootstrap-input-spinner.js",
6
6
  "scripts": {
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "homepage": "https://shaack.com/en/open-source-components",
27
27
  "devDependencies": {
28
- "prismjs": "^1.29.0",
29
- "teevi": "^2.2.4"
28
+ "prismjs": "^1.30.0",
29
+ "teevi": "^2.3.0"
30
30
  }
31
31
  }
@@ -3,49 +3,69 @@
3
3
  * Repository: https://github.com/shaack/bootstrap-input-spinner
4
4
  * License: MIT, see file 'LICENSE'
5
5
  */
6
- const customEditors = {
7
- RawEditor: function (props, element) {
8
- this.parse = function (customFormat) {
9
- // parse nothing
10
- return customFormat
6
+
7
+ export const RawEditor = function (props, element) {
8
+ this.parse = function (customFormat) {
9
+ // parse nothing
10
+ return customFormat
11
+ }
12
+ this.render = function (number) {
13
+ // render raw
14
+ return number
15
+ }
16
+ }
17
+
18
+ export const TimeEditor = function (props, element) {
19
+ // could be implemented more elegant maybe, but works
20
+ this.parse = function (customFormat) {
21
+ let trimmed = customFormat.trim()
22
+ let sign = 1
23
+ if (trimmed.charAt(0) === "-") {
24
+ sign = -1
25
+ trimmed = trimmed.replace("-", "")
11
26
  }
12
- this.render = function (number) {
13
- // render raw
14
- return number
27
+ const parts = trimmed.split(":")
28
+ let hours = 0, minutes
29
+ if (parts[1]) {
30
+ hours = parseInt(parts[0], 10)
31
+ minutes = parseInt(parts[1], 10)
32
+ } else {
33
+ minutes = parseInt(parts[0], 10)
15
34
  }
16
- },
17
- TimeEditor: function (props, element) {
18
- // could be implemented more elegant maybe, but works
19
- this.parse = function (customFormat) {
20
- let trimmed = customFormat.trim()
21
- let sign = 1
22
- if (trimmed.charAt(0) === "-") {
23
- sign = -1
24
- trimmed = trimmed.replace("-", "")
25
- }
26
- const parts = trimmed.split(":")
27
- let hours = 0, minutes
28
- if (parts[1]) {
29
- hours = parseInt(parts[0], 10)
30
- minutes = parseInt(parts[1], 10)
31
- } else {
32
- minutes = parseInt(parts[0], 10)
33
- }
34
- return (hours * 60 + minutes) * sign
35
+ return (hours * 60 + minutes) * sign
36
+ }
37
+ this.render = function (number) {
38
+ let minutes = Math.abs(number % 60)
39
+ if (minutes < 10) {
40
+ minutes = "0" + minutes
35
41
  }
36
- this.render = function (number) {
37
- let minutes = Math.abs(number % 60)
38
- if (minutes < 10) {
39
- minutes = "0" + minutes
40
- }
41
- let hours
42
- if (number >= 0) {
43
- hours = Math.floor(number / 60)
44
- return hours + ":" + minutes
45
- } else {
46
- hours = Math.ceil(number / 60)
47
- return "-" + Math.abs(hours) + ":" + minutes
48
- }
42
+ let hours
43
+ if (number >= 0) {
44
+ hours = Math.floor(number / 60)
45
+ return hours + ":" + minutes
46
+ } else {
47
+ hours = Math.ceil(number / 60)
48
+ return "-" + Math.abs(hours) + ":" + minutes
49
49
  }
50
50
  }
51
51
  }
52
+
53
+ // Deprecated: global `window.customEditors` is kept for backwards compatibility
54
+ // with users who load this file via a classic <script> tag. Prefer the named
55
+ // ES module exports above.
56
+ if (typeof window !== "undefined") {
57
+ let warned = false
58
+ const editors = {RawEditor, TimeEditor}
59
+ window.customEditors = new Proxy(editors, {
60
+ get(target, prop) {
61
+ if (!warned && prop in target) {
62
+ warned = true
63
+ console.warn(
64
+ "bootstrap-input-spinner: window.customEditors is deprecated, " +
65
+ "import {RawEditor, TimeEditor} from 'bootstrap-input-spinner/src/custom-editors.js' instead."
66
+ )
67
+ }
68
+ return target[prop]
69
+ }
70
+ })
71
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Author and copyright: Stefan Haack (https://shaack.com)
3
+ * Repository: https://github.com/shaack/bootstrap-input-spinner
4
+ * License: MIT, see file 'LICENSE'
5
+ */
6
+ import {describe, it, assert} from "../node_modules/teevi/src/teevi.js"
7
+ import {RawEditor, TimeEditor} from "../src/custom-editors.js"
8
+
9
+ describe("RawEditor", () => {
10
+ it("parses input unchanged", () => {
11
+ const editor = new RawEditor({}, document.createElement("input"))
12
+ assert.equal(editor.parse("42"), "42")
13
+ assert.equal(editor.parse("abc"), "abc")
14
+ })
15
+ it("renders number unchanged", () => {
16
+ const editor = new RawEditor({}, document.createElement("input"))
17
+ assert.equal(editor.render(3.14), 3.14)
18
+ assert.equal(editor.render(0), 0)
19
+ })
20
+ })
21
+
22
+ describe("TimeEditor", () => {
23
+ const editor = new TimeEditor({}, document.createElement("input"))
24
+
25
+ it("parses H:MM into total minutes", () => {
26
+ assert.equal(editor.parse("1:30"), 90)
27
+ assert.equal(editor.parse("0:05"), 5)
28
+ assert.equal(editor.parse("10:00"), 600)
29
+ })
30
+ it("parses bare minutes", () => {
31
+ assert.equal(editor.parse("45"), 45)
32
+ })
33
+ it("parses negative times", () => {
34
+ assert.equal(editor.parse("-1:30"), -90)
35
+ assert.equal(editor.parse("-0:15"), -15)
36
+ })
37
+ it("renders positive total minutes as H:MM", () => {
38
+ assert.equal(editor.render(90), "1:30")
39
+ assert.equal(editor.render(5), "0:05")
40
+ assert.equal(editor.render(600), "10:00")
41
+ })
42
+ it("renders zero", () => {
43
+ assert.equal(editor.render(0), "0:00")
44
+ })
45
+ it("renders negative total minutes with leading minus", () => {
46
+ assert.equal(editor.render(-90), "-1:30")
47
+ assert.equal(editor.render(-15), "-0:15")
48
+ })
49
+ it("is round-trip stable for positive values", () => {
50
+ for (const v of [0, 5, 59, 60, 61, 125, 600]) {
51
+ assert.equal(editor.parse(editor.render(v)), v)
52
+ }
53
+ })
54
+ })
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Author and copyright: Stefan Haack (https://shaack.com)
3
+ * Repository: https://github.com/shaack/bootstrap-input-spinner
4
+ * License: MIT, see file 'LICENSE'
5
+ */
6
+ import {describe, it, assert} from "../node_modules/teevi/src/teevi.js"
7
+ import {InputSpinner} from "../src/InputSpinner.js"
8
+ import {RawEditor} from "../src/custom-editors.js"
9
+
10
+ const fixture = document.getElementById("fixture")
11
+
12
+ function createInput(attrs = {}) {
13
+ const input = document.createElement("input")
14
+ input.type = "number"
15
+ for (const [k, v] of Object.entries(attrs)) {
16
+ input.setAttribute(k, String(v))
17
+ }
18
+ fixture.appendChild(input)
19
+ return input
20
+ }
21
+
22
+ function spin(attrs = {}, props) {
23
+ const el = createInput(attrs)
24
+ const spinner = new InputSpinner(el, props)
25
+ return {el, spinner, group: el.nextElementSibling}
26
+ }
27
+
28
+ function clear() {
29
+ fixture.innerHTML = ""
30
+ }
31
+
32
+ function wait(ms = 0) {
33
+ return new Promise(r => setTimeout(r, ms))
34
+ }
35
+
36
+ function pressButton(btn) {
37
+ btn.dispatchEvent(new MouseEvent("mousedown", {button: 0, cancelable: true, bubbles: true}))
38
+ document.body.dispatchEvent(new MouseEvent("mouseup", {button: 0, bubbles: true}))
39
+ }
40
+
41
+ describe("InputSpinner construction", () => {
42
+ it("inserts an input-group after the original element", () => {
43
+ const {el, group} = spin({value: "5"})
44
+ assert.true(group !== null)
45
+ assert.true(group.classList.contains("input-group"))
46
+ assert.equal(group.querySelectorAll("button").length, 2)
47
+ assert.equal(group.querySelectorAll("input").length, 1)
48
+ clear()
49
+ })
50
+ it("hides the original input", () => {
51
+ const {el} = spin({value: "5"})
52
+ assert.equal(el.style.display, "none")
53
+ clear()
54
+ })
55
+ it("marks the original element as a spinner", () => {
56
+ const {el} = spin({value: "5"})
57
+ assert.equal(el["bootstrap-input-spinner"], true)
58
+ assert.equal(typeof el.setValue, "function")
59
+ assert.equal(typeof el.destroyInputSpinner, "function")
60
+ clear()
61
+ })
62
+ it("sets the initial value from the value attribute", () => {
63
+ const {el, group} = spin({value: "7"})
64
+ assert.equal(el.value, "7")
65
+ assert.equal(group.querySelector("input").value, "7")
66
+ clear()
67
+ })
68
+ it("leaves value empty (NaN) when no value attribute is given", () => {
69
+ const {el, group} = spin()
70
+ assert.equal(el.value, "")
71
+ assert.equal(group.querySelector("input").value, "")
72
+ clear()
73
+ })
74
+ })
75
+
76
+ describe("InputSpinner I18n rendering (default editor)", () => {
77
+ it("renders with data-decimals", () => {
78
+ const {group} = spin({value: "4.5", "data-decimals": "2"}, {locale: "en-US"})
79
+ assert.equal(group.querySelector("input").value, "4.50")
80
+ clear()
81
+ })
82
+ it("uses thousands grouping by default", () => {
83
+ const {group} = spin({value: "12345"}, {locale: "en-US"})
84
+ assert.equal(group.querySelector("input").value, "12,345")
85
+ clear()
86
+ })
87
+ it("disables grouping when data-digit-grouping=false", () => {
88
+ const {group} = spin({value: "12345", "data-digit-grouping": "false"}, {locale: "en-US"})
89
+ assert.equal(group.querySelector("input").value, "12345")
90
+ clear()
91
+ })
92
+ it("honors the German locale separators", () => {
93
+ const {group} = spin({value: "1234.5", "data-decimals": "1"}, {locale: "de-DE"})
94
+ // German: '.' thousands, ',' decimal
95
+ assert.equal(group.querySelector("input").value, "1.234,5")
96
+ clear()
97
+ })
98
+ })
99
+
100
+ describe("InputSpinner setValue", () => {
101
+ it("clamps above max", () => {
102
+ const {el, group} = spin({value: "5", min: "0", max: "10"})
103
+ el.setValue(99)
104
+ assert.equal(el.value, "10")
105
+ assert.equal(group.querySelector("input").value, "10")
106
+ clear()
107
+ })
108
+ it("clamps below min", () => {
109
+ const {el, group} = spin({value: "5", min: "0", max: "10"})
110
+ el.setValue(-99)
111
+ assert.equal(el.value, "0")
112
+ assert.equal(group.querySelector("input").value, "0")
113
+ clear()
114
+ })
115
+ it("accepts floats", () => {
116
+ const {el} = spin({value: "0", "data-decimals": "2"}, {locale: "en-US"})
117
+ el.setValue(3.14)
118
+ assert.equal(el.value, "3.14")
119
+ clear()
120
+ })
121
+ it("clears value on NaN", () => {
122
+ const {el, group} = spin({value: "5"})
123
+ el.setValue(NaN)
124
+ assert.equal(el.value, "")
125
+ assert.equal(group.querySelector("input").value, "")
126
+ clear()
127
+ })
128
+ it("is reachable via jQuery val() monkey patch", async () => {
129
+ const {el, group} = spin({value: "1", min: "0", max: "100"})
130
+ window.$(el).val(42)
131
+ await wait()
132
+ assert.equal(el.value, "42")
133
+ assert.equal(group.querySelector("input").value, "42")
134
+ clear()
135
+ })
136
+ })
137
+
138
+ describe("InputSpinner stepping", () => {
139
+ it("increments on the plus button", () => {
140
+ const {el, group} = spin({value: "5", min: "0", max: "100", step: "1"})
141
+ pressButton(group.querySelector(".btn-increment"))
142
+ assert.equal(el.value, "6")
143
+ clear()
144
+ })
145
+ it("decrements on the minus button", () => {
146
+ const {el, group} = spin({value: "5", min: "0", max: "100", step: "1"})
147
+ pressButton(group.querySelector(".btn-decrement"))
148
+ assert.equal(el.value, "4")
149
+ clear()
150
+ })
151
+ it("honors a custom step size", () => {
152
+ const {el, group} = spin({value: "0", min: "0", max: "100", step: "10"})
153
+ pressButton(group.querySelector(".btn-increment"))
154
+ assert.equal(el.value, "10")
155
+ pressButton(group.querySelector(".btn-increment"))
156
+ assert.equal(el.value, "20")
157
+ clear()
158
+ })
159
+ it("does not step past max", () => {
160
+ const {el, group} = spin({value: "9", min: "0", max: "10", step: "1"})
161
+ pressButton(group.querySelector(".btn-increment"))
162
+ pressButton(group.querySelector(".btn-increment"))
163
+ assert.equal(el.value, "10")
164
+ clear()
165
+ })
166
+ it("does not step below min", () => {
167
+ const {el, group} = spin({value: "1", min: "0", max: "10", step: "1"})
168
+ pressButton(group.querySelector(".btn-decrement"))
169
+ pressButton(group.querySelector(".btn-decrement"))
170
+ assert.equal(el.value, "0")
171
+ clear()
172
+ })
173
+ it("arrow-up key steps the value", async () => {
174
+ const {el, group} = spin({value: "5", min: "0", max: "10", step: "1"})
175
+ window.$(group.querySelector("input")).trigger(window.$.Event("keydown", {which: 38}))
176
+ assert.equal(el.value, "6")
177
+ clear()
178
+ })
179
+ it("arrow-down key steps the value", async () => {
180
+ const {el, group} = spin({value: "5", min: "0", max: "10", step: "1"})
181
+ window.$(group.querySelector("input")).trigger(window.$.Event("keydown", {which: 40}))
182
+ assert.equal(el.value, "4")
183
+ clear()
184
+ })
185
+ })
186
+
187
+ describe("InputSpinner events", () => {
188
+ it("dispatches 'input' when stepping", async () => {
189
+ const {el, group} = spin({value: "5", min: "0", max: "10"})
190
+ let fired = 0
191
+ el.addEventListener("input", () => fired++)
192
+ pressButton(group.querySelector(".btn-increment"))
193
+ await wait()
194
+ assert.true(fired >= 1)
195
+ clear()
196
+ })
197
+ it("dispatches 'change' on pointer release", async () => {
198
+ const {el, group} = spin({value: "5", min: "0", max: "10"})
199
+ let fired = 0
200
+ el.addEventListener("change", () => fired++)
201
+ pressButton(group.querySelector(".btn-increment"))
202
+ await wait()
203
+ assert.equal(fired, 1)
204
+ clear()
205
+ })
206
+ })
207
+
208
+ describe("InputSpinner attribute observation", () => {
209
+ it("reflects min/max changes", async () => {
210
+ const {el} = spin({value: "5", min: "0", max: "10"})
211
+ el.setAttribute("max", "7")
212
+ await wait()
213
+ el.setValue(99)
214
+ assert.equal(el.value, "7")
215
+ clear()
216
+ })
217
+ it("reflects disabled attribute", async () => {
218
+ const {el, group} = spin({value: "5"})
219
+ el.setAttribute("disabled", "disabled")
220
+ await wait()
221
+ assert.true(group.querySelector(".btn-increment").disabled)
222
+ assert.true(group.querySelector(".btn-decrement").disabled)
223
+ assert.true(group.querySelector("input").disabled)
224
+ clear()
225
+ })
226
+ it("reflects readonly attribute on visible input", async () => {
227
+ const {el, group} = spin({value: "5"})
228
+ el.setAttribute("readonly", "readonly")
229
+ await wait()
230
+ assert.true(group.querySelector("input").readOnly)
231
+ assert.true(group.querySelector(".btn-increment").disabled)
232
+ clear()
233
+ })
234
+ it("maps form-control-sm to input-group-sm", async () => {
235
+ const el = createInput({value: "5"})
236
+ el.className = "form-control form-control-sm"
237
+ new InputSpinner(el)
238
+ await wait()
239
+ assert.true(el.nextElementSibling.classList.contains("input-group-sm"))
240
+ clear()
241
+ })
242
+ it("maps form-control-lg to input-group-lg", async () => {
243
+ const el = createInput({value: "5"})
244
+ el.className = "form-control form-control-lg"
245
+ new InputSpinner(el)
246
+ await wait()
247
+ assert.true(el.nextElementSibling.classList.contains("input-group-lg"))
248
+ clear()
249
+ })
250
+ })
251
+
252
+ describe("InputSpinner prefix/suffix", () => {
253
+ it("renders a prefix element", () => {
254
+ const {group} = spin({value: "5", "data-prefix": "$"})
255
+ const prefix = group.querySelector(".input-group-text")
256
+ assert.true(prefix !== null)
257
+ assert.equal(prefix.textContent, "$")
258
+ clear()
259
+ })
260
+ it("renders a suffix element", () => {
261
+ const {group} = spin({value: "5", "data-suffix": "kg"})
262
+ const suffix = group.querySelector(".input-group-text")
263
+ assert.true(suffix !== null)
264
+ assert.equal(suffix.textContent, "kg")
265
+ clear()
266
+ })
267
+ })
268
+
269
+ describe("InputSpinner buttonsOnly mode", () => {
270
+ it("makes the input readonly", () => {
271
+ const {group} = spin({value: "5"}, {buttonsOnly: true})
272
+ assert.true(group.querySelector("input").readOnly)
273
+ clear()
274
+ })
275
+ it("still allows button stepping", () => {
276
+ const {el, group} = spin({value: "5", min: "0", max: "10"}, {buttonsOnly: true})
277
+ pressButton(group.querySelector(".btn-increment"))
278
+ assert.equal(el.value, "6")
279
+ clear()
280
+ })
281
+ })
282
+
283
+ describe("InputSpinner custom editor", () => {
284
+ it("uses the supplied editor for rendering", () => {
285
+ const {group} = spin({value: "3.14159"}, {editor: RawEditor})
286
+ assert.equal(group.querySelector("input").value, "3.14159")
287
+ clear()
288
+ })
289
+ })
290
+
291
+ describe("InputSpinner destroy", () => {
292
+ it("removes the input-group and un-hides the original", () => {
293
+ const {el, group} = spin({value: "5"})
294
+ assert.true(group.isConnected)
295
+ el.destroyInputSpinner()
296
+ assert.false(group.isConnected)
297
+ assert.notEqual(el.style.display, "none")
298
+ assert.equal(el["bootstrap-input-spinner"], undefined)
299
+ clear()
300
+ })
301
+ })
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>bootstrap-input-spinner tests</title>
6
+ <link rel="stylesheet"
7
+ href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
8
+ crossorigin="anonymous">
9
+ <script src="https://code.jquery.com/jquery-3.6.0.min.js" crossorigin="anonymous"></script>
10
+ <style>
11
+ body { font-family: sans-serif; padding: 1rem; }
12
+ #fixture { position: absolute; left: -10000px; top: -10000px; }
13
+ </style>
14
+ </head>
15
+ <body>
16
+ <h1>bootstrap-input-spinner tests</h1>
17
+ <div id="fixture"></div>
18
+
19
+ <script src="https://cdn.jsdelivr.net/npm/es-module-shims@1.7.2/dist/es-module-shims.min.js"></script>
20
+ <script type="importmap">
21
+ {
22
+ "imports": {
23
+ "teevi/": "../node_modules/teevi/"
24
+ }
25
+ }
26
+ </script>
27
+ <script type="module">
28
+ import {teevi} from "teevi/src/teevi.js"
29
+ import "./TestInputSpinner.js"
30
+ import "./TestCustomEditors.js"
31
+ teevi.run()
32
+ </script>
33
+ </body>
34
+ </html>