UNPKG

@zeix/ui-element

Version:

UIElement - a HTML-first library for reactive Web Components

504 lines (409 loc) 12.8 kB
<!doctype html> <html> <head> <title>focus Tests</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> </head> <body> <focus-test should-focus="false" target-id="input1"> <input id="input1" type="text" placeholder="First input" /> <input id="input2" type="text" placeholder="Second input" /> <button id="button1">Button 1</button> <textarea id="textarea1" placeholder="Text area"></textarea> <select id="select1"> <option value="1">Option 1</option> <option value="2">Option 2</option> </select> </focus-test> <script type="module"> import { runTests } from '@web/test-runner-mocha' import { assert } from '@esm-bundle/chai' import { component, asString, asBoolean, focus, effect, state, RESET, } from '../../index.dev.js' const animationFrame = async () => new Promise(requestAnimationFrame) component( 'focus-test', { shouldFocus: asBoolean(), targetId: asString(), autoFocus: asBoolean(false), count: 0, }, (el, { first }) => [ // Test focus with boolean predicate first('#input1', focus('shouldFocus')), // Test focus with function predicate first( '#input2', focus(() => el.targetId === 'input2'), ), // Test focus with always true (auto-focus) first('#button1', focus('autoFocus')), // Test focus with complex condition first( '#textarea1', focus(() => el.shouldFocus && el.count > 2), ), // Test focus with string value (should focus when value is truthy) first( '#select1', focus(() => el.targetId === 'select1' ? 'focus-me' : '', ), ), ], ) runTests(() => { describe('focus()', () => { it('should focus element when predicate is true', async () => { const el = document.querySelector('focus-test') const input1 = el.querySelector('#input1') // Mock focus to track calls let focusCalled = false const originalFocus = input1.focus input1.focus = function () { focusCalled = true return originalFocus.call(this) } // Initially shouldFocus is false assert.isFalse( focusCalled, 'Should not focus when predicate is false', ) // Enable focus el.shouldFocus = true assert.isTrue( focusCalled, 'Should focus when predicate becomes true', ) // Restore original method input1.focus = originalFocus }) it('should not focus repeatedly when predicate remains true', async () => { const el = document.querySelector('focus-test') const input1 = el.querySelector('#input1') // Mock focus to count calls let focusCallCount = 0 const originalFocus = input1.focus input1.focus = function () { focusCallCount++ return originalFocus.call(this) } // Set shouldFocus to true el.shouldFocus = true const initialCallCount = focusCallCount // Trigger another update without changing shouldFocus el.targetId = 'changed' assert.equal( focusCallCount, initialCallCount, 'Should not call focus again when predicate remains true', ) // Restore original method input1.focus = originalFocus }) it('should work with function predicates', async () => { const el = document.querySelector('focus-test') const input2 = el.querySelector('#input2') // Mock focus to track calls let focusCalled = false const originalFocus = input2.focus input2.focus = function () { focusCalled = true return originalFocus.call(this) } // Initially targetId is not input2 assert.isFalse( focusCalled, 'Should not focus when function predicate is false', ) // Set targetId to input2 el.targetId = 'input2' assert.isTrue( focusCalled, 'Should focus when function predicate becomes true', ) // Restore original method input2.focus = originalFocus }) it('should work with auto-focus pattern', async () => { const el = document.querySelector('focus-test') const button1 = el.querySelector('#button1') // Mock focus to track calls let focusCalled = false const originalFocus = button1.focus button1.focus = function () { focusCalled = true return originalFocus.call(this) } // Initially autoFocus is false assert.isFalse( focusCalled, 'Should not auto-focus initially', ) // Enable auto-focus el.autoFocus = true assert.isTrue( focusCalled, 'Should focus when auto-focus is enabled', ) // Restore original method button1.focus = originalFocus }) it('should work with complex conditions', async () => { const el = document.querySelector('focus-test') const textarea = el.querySelector('#textarea1') // Mock focus to track calls let focusCalled = false const originalFocus = textarea.focus textarea.focus = function () { focusCalled = true return originalFocus.call(this) } // Initially count is 0 and shouldFocus is false assert.isFalse( focusCalled, 'Should not focus with complex condition initially', ) // Enable shouldFocus but count is still too low el.shouldFocus = true assert.isFalse( focusCalled, 'Should not focus when only part of condition is met', ) // Increase count el.count = 3 assert.isTrue( focusCalled, 'Should focus when complex condition is fully met', ) // Restore original method textarea.focus = originalFocus }) it('should work with truthy string values', async () => { const el = document.querySelector('focus-test') const select = el.querySelector('#select1') // Mock focus to track calls let focusCalled = false const originalFocus = select.focus select.focus = function () { focusCalled = true return originalFocus.call(this) } // Initially targetId is not select1 assert.isFalse( focusCalled, 'Should not focus when string predicate returns empty string', ) // Set targetId to select1 (should return truthy string) el.targetId = 'select1' assert.isTrue( focusCalled, 'Should focus when string predicate returns truthy value', ) // Restore original method select.focus = originalFocus }) it('should handle RESET value properly', async () => { const el = document.querySelector('focus-test') const input1 = el.querySelector('#input1') // Mock focus to count calls let focusCallCount = 0 const originalFocus = input1.focus input1.focus = function () { focusCallCount++ return originalFocus.call(this) } // Change shouldFocus to true, then false, then RESET el.shouldFocus = true el.shouldFocus = false const callCountAfterFalse = focusCallCount el.shouldFocus = RESET // RESET should restore to original false value, so no additional focus call assert.equal( focusCallCount, callCountAfterFalse, 'Should not focus when RESET to original false value', ) // Restore original method input1.focus = originalFocus }) it('should work with signal objects directly', async () => { const focusSignal = state(false) const element = document.createElement('input') element.type = 'text' element.placeholder = 'Signal test' document.body.appendChild(element) try { // Mock focus to track calls let focusCalled = false const originalFocus = element.focus element.focus = function () { focusCalled = true return originalFocus.call(this) } // Create mock host component const host = { getSignal: () => focusSignal, } // Apply focus effect with signal const cleanup = focus(focusSignal)(host, element) assert.isFalse( focusCalled, 'Should not focus when signal is false', ) // Change signal to true focusSignal.set(true) assert.isTrue( focusCalled, 'Should focus when signal becomes true', ) // Change back to false (should not focus again) focusCalled = false focusSignal.set(false) assert.isFalse( focusCalled, 'Should not focus when signal becomes false', ) cleanup() // Restore original method element.focus = originalFocus } finally { element.remove() } }) it('should handle non-focusable elements gracefully', async () => { const nonFocusableSignal = state(true) const element = document.createElement('div') element.textContent = 'Non-focusable element' document.body.appendChild(element) try { // Create mock host component const host = { getSignal: () => nonFocusableSignal, } // This should not throw an error even if element doesn't have focus method const cleanup = focus(nonFocusableSignal)( host, element, ) // Element should still exist and be unchanged assert.equal( element.tagName, 'DIV', 'Element should remain unchanged', ) assert.equal( element.textContent, 'Non-focusable element', 'Content should be unchanged', ) cleanup() } finally { element.remove() } }) it('should work with focus management patterns', async () => { // Test focus management like modal dialogs or form validation const showModalSignal = state(false) const hasErrorSignal = state(false) const modalInput = document.createElement('input') modalInput.type = 'text' modalInput.className = 'modal-input' const errorInput = document.createElement('input') errorInput.type = 'email' errorInput.className = 'error-input' document.body.appendChild(modalInput) document.body.appendChild(errorInput) try { // Mock focus methods let modalFocusCalled = false let errorFocusCalled = false const originalModalFocus = modalInput.focus const originalErrorFocus = errorInput.focus modalInput.focus = function () { modalFocusCalled = true return originalModalFocus.call(this) } errorInput.focus = function () { errorFocusCalled = true return originalErrorFocus.call(this) } // Create mock host component const host = { getSignal: prop => { if (prop === 'showModal') return showModalSignal if (prop === 'hasError') return hasErrorSignal return { get: () => false } }, } // Apply focus effects const cleanup1 = focus(showModalSignal)( host, modalInput, ) const cleanup2 = focus(hasErrorSignal)( host, errorInput, ) // Initially nothing should be focused assert.isFalse( modalFocusCalled, 'Modal input should not be focused initially', ) assert.isFalse( errorFocusCalled, 'Error input should not be focused initially', ) // Show modal showModalSignal.set(true) assert.isTrue( modalFocusCalled, 'Modal input should be focused when modal shows', ) assert.isFalse( errorFocusCalled, 'Error input should not be focused yet', ) // Show error (might override modal focus in real scenario) modalFocusCalled = false hasErrorSignal.set(true) assert.isFalse( modalFocusCalled, 'Modal input should not be focused again', ) assert.isTrue( errorFocusCalled, 'Error input should be focused when error shows', ) // Hide modal and error showModalSignal.set(false) hasErrorSignal.set(false) // No additional focus calls should happen modalFocusCalled = false errorFocusCalled = false cleanup1() cleanup2() // Restore original methods modalInput.focus = originalModalFocus errorInput.focus = originalErrorFocus } finally { modalInput.remove() errorInput.remove() } }) }) }) </script> </body> </html>