]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Add tests for new form components
authorMark Otto <markdotto@gmail.com>
Tue, 6 Jan 2026 06:33:37 +0000 (22:33 -0800)
committerMark Otto <markdotto@gmail.com>
Fri, 9 Jan 2026 04:14:09 +0000 (20:14 -0800)
js/tests/unit/otp-input.spec.js
js/tests/unit/strength.spec.js

index 406f236c851820fd2c7df085645fc37fbfe92ea2..8d9840847321cc76669cc7c9ca5b3584c9b9d798 100644 (file)
@@ -243,6 +243,40 @@ describe('OtpInput', () => {
 
       expect(inputs[0].value).toEqual('1')
     })
+
+    it('should distribute multi-character input across inputs', () => {
+      fixtureEl.innerHTML = getOtpHtml()
+
+      const otpEl = fixtureEl.querySelector('.otp-input')
+      new OtpInput(otpEl) // eslint-disable-line no-new
+      const inputs = otpEl.querySelectorAll('input')
+
+      inputs[0].focus()
+      // Simulate autofill that puts multiple characters in first input
+      inputs[0].value = '1234'
+      inputs[0].dispatchEvent(createEvent('input'))
+
+      expect(inputs[0].value).toEqual('1')
+      expect(inputs[1].value).toEqual('2')
+      expect(inputs[2].value).toEqual('3')
+      expect(inputs[3].value).toEqual('4')
+      expect(document.activeElement).toEqual(inputs[3])
+    })
+
+    it('should not advance when entering digit in last input', () => {
+      fixtureEl.innerHTML = getOtpHtml()
+
+      const otpEl = fixtureEl.querySelector('.otp-input')
+      new OtpInput(otpEl) // eslint-disable-line no-new
+      const inputs = otpEl.querySelectorAll('input')
+
+      inputs[5].focus()
+      inputs[5].value = '9'
+      inputs[5].dispatchEvent(createEvent('input'))
+
+      expect(inputs[5].value).toEqual('9')
+      expect(document.activeElement).toEqual(inputs[5])
+    })
   })
 
   describe('keydown handling', () => {
@@ -301,6 +335,86 @@ describe('OtpInput', () => {
 
       expect(document.activeElement).toEqual(inputs[3])
     })
+
+    it('should not navigate left when at first input', () => {
+      fixtureEl.innerHTML = getOtpHtml()
+
+      const otpEl = fixtureEl.querySelector('.otp-input')
+      new OtpInput(otpEl) // eslint-disable-line no-new
+      const inputs = otpEl.querySelectorAll('input')
+
+      inputs[0].focus()
+
+      const arrowEvent = new KeyboardEvent('keydown', {
+        key: 'ArrowLeft',
+        bubbles: true
+      })
+      inputs[0].dispatchEvent(arrowEvent)
+
+      expect(document.activeElement).toEqual(inputs[0])
+    })
+
+    it('should not navigate right when at last input', () => {
+      fixtureEl.innerHTML = getOtpHtml()
+
+      const otpEl = fixtureEl.querySelector('.otp-input')
+      new OtpInput(otpEl) // eslint-disable-line no-new
+      const inputs = otpEl.querySelectorAll('input')
+
+      inputs[5].focus()
+
+      const arrowEvent = new KeyboardEvent('keydown', {
+        key: 'ArrowRight',
+        bubbles: true
+      })
+      inputs[5].dispatchEvent(arrowEvent)
+
+      expect(document.activeElement).toEqual(inputs[5])
+    })
+
+    it('should shift values left on Delete key', () => {
+      fixtureEl.innerHTML = getOtpHtml()
+
+      const otpEl = fixtureEl.querySelector('.otp-input')
+      const otp = new OtpInput(otpEl)
+      const inputs = otpEl.querySelectorAll('input')
+
+      otp.setValue('123456')
+      inputs[2].focus()
+
+      const deleteEvent = new KeyboardEvent('keydown', {
+        key: 'Delete',
+        bubbles: true
+      })
+      inputs[2].dispatchEvent(deleteEvent)
+
+      expect(inputs[0].value).toEqual('1')
+      expect(inputs[1].value).toEqual('2')
+      expect(inputs[2].value).toEqual('4')
+      expect(inputs[3].value).toEqual('5')
+      expect(inputs[4].value).toEqual('6')
+      expect(inputs[5].value).toEqual('')
+    })
+
+    it('should not move focus on backspace when current input has value', () => {
+      fixtureEl.innerHTML = getOtpHtml()
+
+      const otpEl = fixtureEl.querySelector('.otp-input')
+      new OtpInput(otpEl) // eslint-disable-line no-new
+      const inputs = otpEl.querySelectorAll('input')
+
+      inputs[1].value = '5'
+      inputs[1].focus()
+
+      const backspaceEvent = new KeyboardEvent('keydown', {
+        key: 'Backspace',
+        bubbles: true
+      })
+      inputs[1].dispatchEvent(backspaceEvent)
+
+      // Should stay on same input (browser handles clearing the value)
+      expect(document.activeElement).toEqual(inputs[1])
+    })
   })
 
   describe('paste handling', () => {
index c79a3898484f3db7e4c57c59cfbb41a2b9795312..e65033401cb725367b76e7d2cd0c236864c8de98 100644 (file)
@@ -106,6 +106,40 @@ describe('Strength', () => {
 
       expect(strength._input).toEqual(otherInput)
     })
+
+    it('should handle missing input gracefully', () => {
+      fixtureEl.innerHTML = `
+        <div class="strength" data-bs-strength>
+          <div class="strength-segment"></div>
+          <div class="strength-segment"></div>
+          <div class="strength-segment"></div>
+          <div class="strength-segment"></div>
+        </div>
+      `
+
+      const strengthEl = fixtureEl.querySelector('.strength')
+      const strength = new Strength(strengthEl)
+
+      expect(strength._input).toBeNull()
+      expect(strength.getStrength()).toBeNull()
+    })
+
+    it('should use element directly when input config is an element', () => {
+      fixtureEl.innerHTML = `
+        <div>
+          <input type="password" id="my-password" class="form-control">
+        </div>
+        <div class="strength" data-bs-strength>
+          <div class="strength-segment"></div>
+        </div>
+      `
+
+      const strengthEl = fixtureEl.querySelector('.strength')
+      const inputEl = fixtureEl.querySelector('#my-password')
+      const strength = new Strength(strengthEl, { input: inputEl })
+
+      expect(strength._input).toEqual(inputEl)
+    })
   })
 
   describe('getStrength', () => {