UNPKG

@zeix/ui-element

Version:

UIElement - a HTML-first library for reactive Web Components

535 lines (448 loc) 13.5 kB
<!doctype html> <html> <head> <title>callMethod Tests</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> </head> <body> <call-method-test value="initial" action="focus"> <input type="text" placeholder="Test input" /> <button>Test button</button> <form> <input name="test" value="form-value" /> <button type="submit">Submit</button> </form> <div class="custom-element"></div> </call-method-test> <script type="module"> import { runTests } from '@web/test-runner-mocha' import { assert } from '@esm-bundle/chai' import { component, asString, asBoolean, callMethod, setText, setAttribute, effect, state, RESET, } from '../../index.dev.js' const animationFrame = async () => new Promise(requestAnimationFrame) component( 'call-method-test', { value: asString(), action: asString(), shouldFocus: asBoolean(false), shouldSubmit: asBoolean(false), }, (el, { first }) => [ // Test callMethod with simple method name first('input', callMethod('action')), // Test callMethod with method and arguments first( 'button', callMethod('scrollIntoView', () => ({ behavior: 'smooth', })), ), // Test callMethod with conditional execution first('input', callMethod('focus', 'shouldFocus')), // Test callMethod on form first('form', callMethod('submit', 'shouldSubmit')), // Test callMethod with custom method first( '.custom-element', callMethod('customMethod', () => [ el.value, 'extra-arg', ]), ), ], ) runTests(() => { describe('callMethod()', () => { it('should call method when property value matches method name', async () => { const el = document.querySelector('call-method-test') const input = el.querySelector('input') // Mock the focus method to track calls let focusCalled = false const originalFocus = input.focus input.focus = function () { focusCalled = true return originalFocus.call(this) } // Initially action is "focus", so focus should be called assert.isTrue( focusCalled, 'Should call focus method when action is "focus"', ) // Reset and change action focusCalled = false el.action = 'blur' // Now blur should be called (but we can't easily test blur) // The important thing is that focus wasn't called again assert.isFalse( focusCalled, 'Should not call focus when action changes', ) // Restore original method input.focus = originalFocus }) it('should call method with arguments from function', async () => { const el = document.querySelector('call-method-test') const button = el.querySelector('button') // Mock scrollIntoView to track arguments let scrollCalled = false let scrollArgs = null button.scrollIntoView = function (options) { scrollCalled = true scrollArgs = options } // Set action to trigger the method el.action = 'scrollIntoView' assert.isTrue( scrollCalled, 'Should call scrollIntoView method', ) assert.deepEqual( scrollArgs, { behavior: 'smooth' }, 'Should pass correct arguments to method', ) }) it('should call method conditionally based on predicate', async () => { const el = document.querySelector('call-method-test') const input = el.querySelector('input') // Mock focus method let focusCallCount = 0 const originalFocus = input.focus input.focus = function () { focusCallCount++ return originalFocus.call(this) } // Initially shouldFocus is false, so focus should not be called assert.equal( focusCallCount, 0, 'Should not call focus when shouldFocus is false', ) // Enable focus el.shouldFocus = true assert.equal( focusCallCount, 1, 'Should call focus when shouldFocus becomes true', ) // Disable focus again el.shouldFocus = false // Focus should not be called again assert.equal( focusCallCount, 1, 'Should not call focus when shouldFocus becomes false', ) // Restore original method input.focus = originalFocus }) it('should handle form submission conditionally', async () => { const el = document.querySelector('call-method-test') const form = el.querySelector('form') // Mock submit method and preventDefault to avoid actual submission let submitCalled = false const originalSubmit = form.submit form.submit = function () { submitCalled = true } // Add event listener to prevent actual form submission form.addEventListener('submit', e => e.preventDefault()) // Initially shouldSubmit is false assert.isFalse( submitCalled, 'Should not submit form when shouldSubmit is false', ) // Enable submission el.shouldSubmit = true assert.isTrue( submitCalled, 'Should submit form when shouldSubmit becomes true', ) // Restore original method form.submit = originalSubmit }) it('should call custom methods with multiple arguments', async () => { const el = document.querySelector('call-method-test') const customElement = el.querySelector('.custom-element') // Add custom method to element let methodCalled = false let methodArgs = null customElement.customMethod = function (...args) { methodCalled = true methodArgs = args } // Set action to trigger custom method el.action = 'customMethod' assert.isTrue(methodCalled, 'Should call custom method') assert.deepEqual( methodArgs, ['initial', 'extra-arg'], 'Should pass correct arguments to custom method', ) // Change value and test again methodCalled = false el.value = 'updated' assert.isTrue( methodCalled, 'Should call custom method again when value changes', ) assert.deepEqual( methodArgs, ['updated', 'extra-arg'], 'Should pass updated arguments to custom method', ) }) it('should handle RESET value properly', async () => { const el = document.querySelector('call-method-test') const input = el.querySelector('input') // Mock focus method let focusCallCount = 0 const originalFocus = input.focus input.focus = function () { focusCallCount++ return originalFocus.call(this) } // Change action then reset el.action = 'blur' el.action = RESET // Should call focus again (original action was "focus") assert.equal( focusCallCount, 1, 'Should call method when RESET to original value', ) // Restore original method input.focus = originalFocus }) it('should work with signal objects directly', async () => { const methodSignal = state('click') const element = document.createElement('button') element.textContent = 'Test Button' document.body.appendChild(element) try { // Mock click method let clickCalled = false const originalClick = element.click element.click = function () { clickCalled = true return originalClick.call(this) } // Create mock host component const host = { getSignal: () => methodSignal, } // Apply callMethod with signal const cleanup = callMethod(methodSignal)( host, element, ) assert.isTrue( clickCalled, 'Should call method from signal value', ) // Change signal value clickCalled = false methodSignal.set('focus') // Focus should be called now (if element supports it) assert.isFalse( clickCalled, 'Should not call click when signal changes', ) cleanup() // Restore original method element.click = originalClick } finally { element.remove() } }) it('should handle non-existent methods gracefully', async () => { const element = document.createElement('div') document.body.appendChild(element) try { // Create mock host component const host = { getSignal: () => ({ get: () => 'nonExistentMethod', }), } // This should not throw an error const cleanup = callMethod( () => 'nonExistentMethod', )(host, element) // Element should still exist and be unchanged assert.equal( element.tagName, 'DIV', 'Element should remain unchanged', ) cleanup() } finally { element.remove() } }) it('should work with method arguments as signals', async () => { const argsSignal = state({ behavior: 'auto' }) const element = document.createElement('div') document.body.appendChild(element) try { // Mock scrollIntoView let scrollCalled = false let scrollArgs = null element.scrollIntoView = function (options) { scrollCalled = true scrollArgs = options } // Create mock host component const host = { getSignal: () => argsSignal, } // Apply callMethod with signal arguments const cleanup = callMethod( 'scrollIntoView', argsSignal, )(host, element) assert.isTrue( scrollCalled, 'Should call method with signal arguments', ) assert.deepEqual( scrollArgs, { behavior: 'auto' }, 'Should pass signal value as arguments', ) // Update signal scrollCalled = false argsSignal.set({ behavior: 'smooth', block: 'center', }) assert.isTrue( scrollCalled, 'Should call method again when arguments change', ) assert.deepEqual( scrollArgs, { behavior: 'smooth', block: 'center' }, 'Should pass updated signal value as arguments', ) cleanup() } finally { element.remove() } }) it('should handle complex reactive patterns', async () => { const actionSignal = state('none') const enabledSignal = state(false) const valueSignal = state('test') const element = document.createElement('input') element.type = 'text' document.body.appendChild(element) try { // Mock methods let focusCalled = false let selectCalled = false let setValue = null const originalFocus = element.focus const originalSelect = element.select const originalSetValue = element.value element.focus = function () { focusCalled = true return originalFocus.call(this) } element.select = function () { selectCalled = true return originalSelect.call(this) } Object.defineProperty(element, 'value', { get: () => originalSetValue, set: val => { setValue = val originalSetValue = val }, }) // Create mock host component const host = { getSignal: prop => { if (prop === 'action') return actionSignal if (prop === 'enabled') return enabledSignal if (prop === 'value') return valueSignal return { get: () => null } }, } // Apply multiple callMethod effects const cleanup1 = callMethod( 'focus', () => actionSignal.get() === 'focus' && enabledSignal.get(), )(host, element) const cleanup2 = callMethod( 'select', () => actionSignal.get() === 'select' && enabledSignal.get(), )(host, element) // Initially nothing should happen assert.isFalse( focusCalled, 'Should not call focus initially', ) assert.isFalse( selectCalled, 'Should not call select initially', ) // Enable and set action to focus enabledSignal.set(true) actionSignal.set('focus') assert.isTrue( focusCalled, 'Should call focus when enabled and action is focus', ) assert.isFalse( selectCalled, 'Should not call select', ) // Reset and change to select focusCalled = false actionSignal.set('select') assert.isFalse( focusCalled, 'Should not call focus when action changes', ) assert.isTrue( selectCalled, 'Should call select when action is select', ) // Disable selectCalled = false enabledSignal.set(false) assert.isFalse( selectCalled, 'Should not call select when disabled', ) cleanup1() cleanup2() // Restore original methods element.focus = originalFocus element.select = originalSelect } finally { element.remove() } }) }) }) </script> </body> </html>