import { Injector } from '@furystack/inject' import { ObservableValue, usingAsync } from '@furystack/utils' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { initializeShadeRoot } from './initialize.js' import { createComponent } from './shade-component.js' import { flushUpdates, Shade } from './shade.js' describe('VNode reconciliation integration tests', () => { beforeEach(() => { document.body.innerHTML = '
' }) afterEach(() => { document.body.innerHTML = '' }) describe('focus preservation', () => { it('should preserve focus on an input element across re-renders', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root') as HTMLDivElement const ExampleComponent = Shade({ customElementName: 'morph-focus-test', render: ({ useState }) => { const [label, setLabel] = useState('label', 'initial') return (
) }, }) initializeShadeRoot({ injector, rootElement, jsxElement: , }) await flushUpdates() const input = document.getElementById('my-input') as HTMLInputElement input.focus() expect(document.activeElement).toBe(input) // Trigger a re-render by clicking the button document.getElementById('update-label')?.click() await flushUpdates() // The label should have updated expect(document.querySelector('label')?.textContent).toBe('updated') // The same input element should still be in the DOM and focused const inputAfter = document.getElementById('my-input') as HTMLInputElement expect(inputAfter).toBe(input) expect(document.activeElement).toBe(input) }) }) it('should preserve focus on a textarea across re-renders', async () => { await usingAsync(new Injector(), async (injector) => { const rootElement = document.getElementById('root') as HTMLDivElement const ExampleComponent = Shade({ customElementName: 'morph-focus-textarea-test', render: ({ useState }) => { const [count, setCount] = useState('count', 0) return (
Count: {count}