import { h, render, mount, nextTick, type WDom } from 'lithent'; import { createContext } from '@/index'; // Type for multiple states type AppContext = { user: string; theme: string; count: number; }; const testContext = createContext(); const { Provider, contextState, useContext } = testContext; const Component = mount(() => { const userState = contextState('Alice'); const themeState = contextState('dark'); const countState = contextState(0); return () => (

Multiple State Context Example

{/* Pass all states */}

Full Subscription (no keys)

Selective Subscription (user only)

Selective Subscription (theme + count)

); }); // Full subscription (no keys specified) const ChildrenFullSubscribe = mount<{ jj: number }>(renew => { const ctx = useContext(testContext, renew); // Subscribe to all keys return () => (

User: {ctx.user?.value ?? 'N/A'}

Theme: {ctx.theme?.value ?? 'N/A'}

Count: {ctx.count?.value ?? 'N/A'}

(Renders on ANY state change)
); }); // Subscribe to user only const ChildrenUserOnly = mount<{ jj: number }>(renew => { const ctx = useContext(testContext, renew, ['user']); // Subscribe to user only return () => (

User: {ctx.user?.value ?? 'N/A'}

(Only renders when user changes)
); }); // Subscribe to theme + count const ChildrenThemeCount = mount<{ jj: number }>(renew => { const ctx = useContext(testContext, renew, ['theme', 'count']); // Subscribe to theme and count only return () => (

Theme: {ctx.theme?.value ?? 'N/A'}

Count: {ctx.count?.value ?? 'N/A'}

(Only renders when theme or count changes)
); }); // Single state pattern test (UserProvider pattern) const singleContext = createContext<{ state: number }>(); const { Provider: SingleProvider, contextState: singleContextState, useContext: useSingleContext, } = singleContext; const UserProvider = mount((_renew, _props, children: WDom[]) => { const contextRef = singleContextState(42); return () => {children}; }); const SingleChild = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (

UserProvider Pattern Test

Value: {ctx.state?.value ?? 'N/A'}

); }); const ComponentWithUserProvider = mount(() => { return () => (

UserProvider Pattern (Nested Provider)

); }); // Component that changes state inside Provider const SingleChildWithButton = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (

UserProvider Pattern Test

Value: {ctx.state?.value ?? 'N/A'}

); }); const ComponentWithUserProviderAndButton = mount(() => { return () => (

UserProvider Pattern (With Update Test)

); }); // Pattern that receives children as props and renders them directly under Provider const DirectChildrenProvider = mount((_renew, _props, children: WDom[]) => { const contextRef = singleContextState(100); return () => {children}; }); const DirectChild1 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (

DirectChild1 - Value: {ctx.state?.value ?? 'N/A'}

); }); const DirectChild2 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (

DirectChild2 - Value: {ctx.state?.value ?? 'N/A'}

); }); const ComponentWithDirectChildren = mount(() => { return () => (

Direct Children Pattern (children passed via props)

UserProvider renders children received as props directly under Provider

); }); // Test auto-renew feature with contextState(value, renew) const AutoRenewProvider = mount((renew, _props, children: WDom[]) => { const contextRef = singleContextState(200, renew); // Pass renew to auto-update return () => (
{children}
); }); const AutoRenewChild = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (

Auto Renew Pattern Test

Value: {ctx.state?.value ?? 'N/A'}

(Provider has renew in contextState)
); }); const ComponentWithAutoRenew = mount(() => { return () => (

Auto Renew Pattern (contextState with renew)

Provider passes renew to contextState for automatic updates

); }); const testWrap = document.getElementById('root'); // Render all examples in dev mode if (testWrap) { render(




, testWrap ); } // Test component - tests pattern of receiving children as props and passing to Provider const testContainer = document.createElement('div'); // Test if children passed via props correctly find the Provider const TestDirectProvider = mount((_renew, _props, children: WDom[]) => { const stateRef = singleContextState(100); return () => {children}; }); const TestDirectChild1 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (
{ctx.state?.value ?? 'N/A'}
); }); const TestDirectChild2 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (
{ctx.state?.value ?? 'N/A'}
); }); const TestDirectComponent = mount(() => { return () => (
); }); // Render to actual DOM for manual testing in dev mode if (testWrap) { render(, testContainer); } if (import.meta.vitest) { const { it, expect } = import.meta.vitest; it('Children passed as props should find the correct Provider', async () => { // Create independent test environment const container = document.createElement('div'); const TestProvider = mount((_renew, _props, children: WDom[]) => { const stateRef = singleContextState(100); return () => {children}; }); const Child1 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (
{ctx.state?.value ?? 'N/A'}
); }); const Child2 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (
{ctx.state?.value ?? 'N/A'}
); }); const TestComp = mount(() => { return () => ( ); }); render(, container); await nextTick(); const value1 = container.querySelector('.value1'); const value2 = container.querySelector('.value2'); expect(value1?.textContent).toBe('100'); expect(value2?.textContent).toBe('100'); }); it('Children passed as props should share the same Provider state', async () => { // Create independent test environment const container = document.createElement('div'); const TestProvider = mount((_renew, _props, children: WDom[]) => { const stateRef = singleContextState(100); return () => {children}; }); const Child1 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (
{ctx.state?.value ?? 'N/A'}
); }); const Child2 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (
{ctx.state?.value ?? 'N/A'}
); }); const TestComp = mount(() => { return () => ( ); }); render(, container); const btn1 = container.querySelector('.btn1') as HTMLButtonElement; btn1?.click(); await nextTick(); const value1 = container.querySelector('.value1'); const value2 = container.querySelector('.value2'); expect(value1?.textContent).toBe('110'); expect(value2?.textContent).toBe('110'); }); it('Multiple children should update together when Provider state changes', async () => { // Create independent test environment const container = document.createElement('div'); const TestProvider = mount((_renew, _props, children: WDom[]) => { const stateRef = singleContextState(100); return () => {children}; }); const Child1 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (
{ctx.state?.value ?? 'N/A'}
); }); const Child2 = mount<{ jj: number }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => (
{ctx.state?.value ?? 'N/A'}
); }); const TestComp = mount(() => { return () => ( ); }); render(, container); const btn2 = container.querySelector('.btn2') as HTMLButtonElement; btn2?.click(); await nextTick(); const value1 = container.querySelector('.value1'); const value2 = container.querySelector('.value2'); expect(value1?.textContent).toBe('95'); expect(value2?.textContent).toBe('95'); }); it('Dynamically added children should find the Provider', async () => { // Create independent test environment const container = document.createElement('div'); const TestProvider = mount((_renew, _props, children: WDom[]) => { const stateRef = singleContextState(100); return () => {children}; }); const ConditionalChild = mount<{ id: string }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return props => (
{ctx.state?.value ?? 'N/A'}
); }); const TestComp = mount(renew => { let showChild2 = false; return () => (
{showChild2 && }
); }); render(, container); expect(container.querySelector('.child-1')).not.toBeNull(); expect(container.querySelector('.child-2')).toBeNull(); const toggleBtn = container.querySelector('.toggle') as HTMLButtonElement; toggleBtn?.click(); await nextTick(); const child2 = container.querySelector('.child-2'); const child2Value = container.querySelector('.child-2 .value'); expect(child2).not.toBeNull(); expect(child2Value?.textContent).toBe('100'); }); it('Dynamically added children should share the same Provider state', async () => { // Create independent test environment const container = document.createElement('div'); const TestProvider = mount((_renew, _props, children: WDom[]) => { const stateRef = singleContextState(100); return () => {children}; }); const ConditionalChild = mount<{ id: string }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return props => (
{ctx.state?.value ?? 'N/A'}
); }); const StateUpdater = mount(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => ( ); }); const TestComp = mount(renew => { let showChild2 = false; let showChild3 = false; return () => (
{showChild2 && } {showChild3 && }
); }); render(, container); const toggle2 = container.querySelector('.toggle2') as HTMLButtonElement; const toggle3 = container.querySelector('.toggle3') as HTMLButtonElement; const updater = container.querySelector('.updater') as HTMLButtonElement; // Add child2 and child3 toggle2?.click(); await nextTick(); toggle3?.click(); await nextTick(); let value1 = container.querySelector('.child-1 .value'); let value2 = container.querySelector('.child-2 .value'); let value3 = container.querySelector('.child-3 .value'); expect(value1?.textContent).toBe('100'); expect(value2?.textContent).toBe('100'); expect(value3?.textContent).toBe('100'); // Update state updater?.click(); await nextTick(); value1 = container.querySelector('.child-1 .value'); value2 = container.querySelector('.child-2 .value'); value3 = container.querySelector('.child-3 .value'); expect(value1?.textContent).toBe('150'); expect(value2?.textContent).toBe('150'); expect(value3?.textContent).toBe('150'); }); it('contextState with renew should auto-update component', async () => { // Create independent test environment const container = document.createElement('div'); // Test simple component without Provider first const SimpleComp = mount(renew => { const state = singleContextState(200, renew); return () => (
{state.value}
); }); render(, container); await nextTick(); const display = container.querySelector('.simple-display'); const btn = container.querySelector('.simple-btn') as HTMLButtonElement; expect(display?.textContent).toBe('200'); btn?.click(); await nextTick(); expect(display?.textContent).toBe('201'); }); it('Removed and re-added children should still work correctly', async () => { // Create independent test environment const container = document.createElement('div'); const TestProvider = mount((_renew, _props, children: WDom[]) => { const stateRef = singleContextState(100); return () => {children}; }); const ConditionalChild = mount<{ id: string }>(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return props => (
{ctx.state?.value ?? 'N/A'}
); }); const StateUpdater = mount(renew => { const ctx = useSingleContext(singleContext, renew, ['state']); return () => ( ); }); const TestComp = mount(renew => { let showChild2 = true; return () => (
{showChild2 && }
); }); render(, container); const toggle2 = container.querySelector('.toggle2') as HTMLButtonElement; const updater = container.querySelector('.updater') as HTMLButtonElement; // Remove child2 toggle2?.click(); await nextTick(); expect(container.querySelector('.child-2')).toBeNull(); // Update state (100 + 50 = 150) updater?.click(); await nextTick(); // Add child2 again toggle2?.click(); await nextTick(); const child2 = container.querySelector('.child-2'); const child2Value = container.querySelector('.child-2 .value'); expect(child2).not.toBeNull(); expect(child2Value?.textContent).toBe('150'); }); }